From 9b98181d280cae25c52e397c228c8646c7bd851e Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 12 Sep 2025 12:43:56 -0500 Subject: [PATCH] Process tileset. --- CMakeLists.txt | 4 +- assets/CMakeLists.txt | 7 +- src/display/CMakeLists.txt | 1 + src/display/display.c | 2 +- src/display/scene/overworld/sceneoverworld.c | 20 ++++- src/display/scene/overworld/sceneoverworld.h | 3 +- src/display/scene/scene.h | 3 +- src/display/scene/scenemanager.c | 5 +- src/display/scene/scenemanager.h | 2 +- src/display/tileset.c | 30 +++++++ src/display/tileset.h | 32 ++++++++ src/display/ui/ui.c | 4 +- src/display/ui/uiconsole.c | 2 +- src/display/ui/uitext.c | 32 ++++---- src/display/ui/uitext.h | 8 +- tools/assetstool/processasset.py | 5 +- tools/assetstool/processimage.py | 8 +- tools/assetstool/processtileset.py | 84 ++++++++++++++++++++ 18 files changed, 205 insertions(+), 47 deletions(-) create mode 100644 src/display/tileset.c create mode 100644 src/display/tileset.h create mode 100644 tools/assetstool/processtileset.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 7577a71..c0c9fdc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) if(NOT DEFINED DUSK_TARGET_SYSTEM) - # set(DUSK_TARGET_SYSTEM "linux") - set(DUSK_TARGET_SYSTEM "psp") + set(DUSK_TARGET_SYSTEM "linux") + # set(DUSK_TARGET_SYSTEM "psp") endif() # Prep cache diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index 7136e37..9a17f3d 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -4,8 +4,7 @@ # https://opensource.org/licenses/MIT add_asset(PALETTE first.palette.png) -add_asset(IMAGE font_minogram.png type=ALPHA) -add_asset(IMAGE entities.png type=PALETTIZED) +add_asset(TILESET font_minogram.png type=ALPHA tileWidth=6 tileHeight=10 columns=16 rows=6) +add_asset(TILESET entities.png type=PALETTIZED tileWidth=16 tileHeight=16) add_asset(CONFIG init.dcf) -add_asset(CONFIG init_psp.dcf) -# add_asset(TILESET entities.tsx) \ No newline at end of file +add_asset(CONFIG init_psp.dcf) \ No newline at end of file diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt index 752cf53..f01b3b2 100644 --- a/src/display/CMakeLists.txt +++ b/src/display/CMakeLists.txt @@ -8,6 +8,7 @@ target_sources(${DUSK_TARGET_NAME} PRIVATE display.c camera.c + tileset.c ) # Subdirectories diff --git a/src/display/display.c b/src/display/display.c index 8e3315d..b9984ad 100644 --- a/src/display/display.c +++ b/src/display/display.c @@ -67,7 +67,7 @@ errorret_t displayInit(void) { frameBufferInitBackbuffer(); spriteBatchInit(); errorChain(uiInit()); - sceneManagerInit(); + errorChain(sceneManagerInit()); errorOk(); } diff --git a/src/display/scene/overworld/sceneoverworld.c b/src/display/scene/overworld/sceneoverworld.c index 3a9d955..aa670f0 100644 --- a/src/display/scene/overworld/sceneoverworld.c +++ b/src/display/scene/overworld/sceneoverworld.c @@ -14,18 +14,24 @@ #include "asset/assetmanager.h" #include "assert/assert.h" +#include "display/tileset/tileset_entities.h" + sceneoverworld_t SCENE_OVERWORLD; asset_t *testAsset; ref_t testAssetRef; -void sceneOverworldInit(void) { +errorret_t sceneOverworldInit(void) { cameraInit(&SCENE_OVERWORLD.camera); glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, SCENE_OVERWORLD.camera.lookat.up); scene_t *scene = &SCENE_MANAGER_SCENES[SCENE_TYPE_OVERWORLD]; scene->flags |= SCENE_FLAG_ACTIVE | SCENE_FLAG_VISIBLE; - assetManagerLoadAsset("entities.dpi", &testAsset, &testAssetRef); + errorChain(assetManagerLoadAsset( + TILESET_ENTITIES.image, &testAsset, &testAssetRef + )); + + errorOk(); } void sceneOverworldUpdate(void) { @@ -79,11 +85,17 @@ void sceneOverworldRenderEntity(const entity_t *entity) { assertTrue(entity->type < ENTITY_TYPE_COUNT, "Invalid entity type"); assertTrue(entity->type != ENTITY_TYPE_NULL, "Cannot have NULL entity type"); + vec4 uv; + tilesetTileGetUV(&TILESET_ENTITIES, 0, uv); + // For now, just draw a placeholder quad. spriteBatchPush( &testAsset->paletteImage.texture, - entity->x, entity->y, entity->x + 32.0f, entity->y + 32.0f, - COLOR_WHITE, 0.0f, 0.0f, 0.125f, 0.125f + entity->x, entity->y, + entity->x + TILESET_ENTITIES.tileWidth, + entity->y + TILESET_ENTITIES.tileHeight, + COLOR_WHITE, + uv[0], uv[1], uv[2], uv[3] ); } diff --git a/src/display/scene/overworld/sceneoverworld.h b/src/display/scene/overworld/sceneoverworld.h index 6a1c736..c5aa8b2 100644 --- a/src/display/scene/overworld/sceneoverworld.h +++ b/src/display/scene/overworld/sceneoverworld.h @@ -8,6 +8,7 @@ #pragma once #include "display/camera.h" #include "rpg/world/map.h" +#include "error/error.h" typedef struct { camera_t camera; @@ -18,7 +19,7 @@ extern sceneoverworld_t SCENE_OVERWORLD; /** * Initialize the overworld scene. */ -void sceneOverworldInit(void); +errorret_t sceneOverworldInit(void); /** * Update the overworld scene. diff --git a/src/display/scene/scene.h b/src/display/scene/scene.h index 1a01c05..69c9906 100644 --- a/src/display/scene/scene.h +++ b/src/display/scene/scene.h @@ -7,12 +7,13 @@ #pragma once #include "dusk.h" +#include "error/error.h" #define SCENE_FLAG_VISIBLE (1 << 0) #define SCENE_FLAG_ACTIVE (1 << 1) typedef struct { - void (*init)(void); + errorret_t (*init)(void); void (*update)(void); void (*render)(void); void (*dispose)(void); diff --git a/src/display/scene/scenemanager.c b/src/display/scene/scenemanager.c index 13d6225..211799d 100644 --- a/src/display/scene/scenemanager.c +++ b/src/display/scene/scenemanager.c @@ -22,9 +22,10 @@ scene_t SCENE_MANAGER_SCENES[SCENE_TYPE_COUNT] = { } }; -void sceneManagerInit(void) { +errorret_t sceneManagerInit(void) { scene_t *initial = &SCENE_MANAGER_SCENES[SCENE_TYPE_INITIAL]; - if(initial->init != NULL) initial->init(); + if(initial->init != NULL) errorChain(initial->init()); + errorOk(); } void sceneManagerUpdate(void) { diff --git a/src/display/scene/scenemanager.h b/src/display/scene/scenemanager.h index 2fb729d..88e6aab 100644 --- a/src/display/scene/scenemanager.h +++ b/src/display/scene/scenemanager.h @@ -15,7 +15,7 @@ typedef struct { extern scenemanager_t SCENE_MANAGER; extern scene_t SCENE_MANAGER_SCENES[SCENE_TYPE_COUNT]; -void sceneManagerInit(void); +errorret_t sceneManagerInit(void); void sceneManagerUpdate(void); void sceneManagerRender(void); void sceneManagerDispose(void); \ No newline at end of file diff --git a/src/display/tileset.c b/src/display/tileset.c new file mode 100644 index 0000000..5dc79d7 --- /dev/null +++ b/src/display/tileset.c @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "tileset.h" + +void tilesetTileGetUV( + const tileset_t *tileset, + const uint16_t tileIndex, + vec4 outUV +) { + const uint16_t column = tileIndex % tileset->columns; + const uint16_t row = tileIndex / tileset->columns; + tilesetPositionGetUV(tileset, column, row, outUV); +} + +void tilesetPositionGetUV( + const tileset_t *tileset, + const uint16_t column, + const uint16_t row, + vec4 outUV +) { + 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]; +} \ No newline at end of file diff --git a/src/display/tileset.h b/src/display/tileset.h new file mode 100644 index 0000000..6ec5588 --- /dev/null +++ b/src/display/tileset.h @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef struct tileset_s { + const uint16_t tileWidth; + const uint16_t tileHeight; + const uint16_t tileCount; + const uint16_t columns; + const uint16_t rows; + const vec2 uv; + const char_t *image; +} tileset_t; + +void tilesetTileGetUV( + const tileset_t *tileset, + const uint16_t tileIndex, + vec4 outUV +); + +void tilesetPositionGetUV( + const tileset_t *tileset, + const uint16_t column, + const uint16_t row, + vec4 outUV +); \ No newline at end of file diff --git a/src/display/ui/ui.c b/src/display/ui/ui.c index 675da34..bc5fa44 100644 --- a/src/display/ui/ui.c +++ b/src/display/ui/ui.c @@ -32,7 +32,7 @@ errorret_t uiInit(void) { UI.camera.viewType = CAMERA_VIEW_TYPE_MATRIX; glm_mat4_identity(UI.camera.view); - UI.scale = 2.0f; + UI.scale = 1.0f; uiFPSInit(); @@ -54,7 +54,7 @@ void uiRender(void) { cameraPushMatrix(&UI.camera); uiConsoleRender(); - // uiFPSRender(); + uiFPSRender(); spriteBatchFlush(); cameraPopMatrix(); diff --git a/src/display/ui/uiconsole.c b/src/display/ui/uiconsole.c index d28ce3f..3164ae2 100644 --- a/src/display/ui/uiconsole.c +++ b/src/display/ui/uiconsole.c @@ -21,7 +21,7 @@ void uiConsoleRender(void) { i--; continue; } - uiTextDraw(0, i * UI_TEXT_TILE_HEIGHT, line, COLOR_WHITE); + uiTextDraw(0, i * TILESET_FONT_MINOGRAM.tileHeight, line, COLOR_WHITE); i--; } while(i > 0); } \ No newline at end of file diff --git a/src/display/ui/uitext.c b/src/display/ui/uitext.c index 75d729d..c3de09d 100644 --- a/src/display/ui/uitext.c +++ b/src/display/ui/uitext.c @@ -17,7 +17,7 @@ errorret_t uiTextInit(void) { memoryZero(&UI_TEXT, sizeof(uitext_t)); errorChain(assetManagerLoadAsset( - "font_minogram.dai", &UI_TEXT.asset, &UI_TEXT.assetRef + TILESET_FONT_MINOGRAM.image, &UI_TEXT.asset, &UI_TEXT.assetRef )); errorOk(); } @@ -35,29 +35,25 @@ void uiTextDrawChar( const color_t color ) { int32_t tileIndex = (int32_t)(c) - UI_TEXT_CHAR_START; - if(tileIndex < 0 || tileIndex >= UI_TEXT_TILE_COUNT) { + if(tileIndex < 0 || tileIndex >= TILESET_FONT_MINOGRAM.tileCount) { tileIndex = ((int32_t)'@') - UI_TEXT_CHAR_START; } assertTrue( - tileIndex >= 0 && tileIndex <= UI_TEXT_TILE_COUNT, + tileIndex >= 0 && tileIndex <= TILESET_FONT_MINOGRAM.tileCount, "Character is out of bounds for font tiles" ); - const float_t w = (float)UI_TEXT.asset->alphaImage.texture.width; - const float_t h = (float)UI_TEXT.asset->alphaImage.texture.height; - const int32_t tileX = (tileIndex % UI_TEXT_COLUMN_COUNT); - const int32_t tileY = (tileIndex / UI_TEXT_COLUMN_COUNT); + vec4 uv; + tilesetTileGetUV(&TILESET_FONT_MINOGRAM, tileIndex, uv); spriteBatchPush( &UI_TEXT.asset->alphaImage.texture, x, y, - x + UI_TEXT_TILE_WIDTH, y + UI_TEXT_TILE_HEIGHT, + x + TILESET_FONT_MINOGRAM.tileWidth, + y + TILESET_FONT_MINOGRAM.tileHeight, color, - (tileX * UI_TEXT_TILE_WIDTH) / w, - (tileY * UI_TEXT_TILE_HEIGHT) / h, - ((tileX + 1) * UI_TEXT_TILE_WIDTH) / w, - ((tileY + 1) * UI_TEXT_TILE_HEIGHT) / h + uv[0], uv[1], uv[2], uv[3] ); } @@ -77,17 +73,17 @@ void uiTextDraw( while((c = text[i++]) != '\0') { if(c == '\n') { posX = x; - posY += UI_TEXT_TILE_HEIGHT; + posY += TILESET_FONT_MINOGRAM.tileHeight; continue; } if(c == ' ') { - posX += UI_TEXT_TILE_WIDTH; + posX += TILESET_FONT_MINOGRAM.tileWidth; continue; } uiTextDrawChar(posX, posY, c, color); - posX += UI_TEXT_TILE_WIDTH; + posX += TILESET_FONT_MINOGRAM.tileWidth; } } @@ -101,7 +97,7 @@ void uiTextMeasure( assertNotNull(outHeight, "Output height pointer cannot be NULL"); int32_t width = 0; - int32_t height = UI_TEXT_TILE_HEIGHT; + int32_t height = TILESET_FONT_MINOGRAM.tileHeight; int32_t lineWidth = 0; char_t c; @@ -112,11 +108,11 @@ void uiTextMeasure( width = lineWidth; } lineWidth = 0; - height += UI_TEXT_TILE_HEIGHT; + height += TILESET_FONT_MINOGRAM.tileHeight; continue; } - lineWidth += UI_TEXT_TILE_WIDTH; + lineWidth += TILESET_FONT_MINOGRAM.tileWidth; } if(lineWidth > width) { diff --git a/src/display/ui/uitext.h b/src/display/ui/uitext.h index e3878d8..973606e 100644 --- a/src/display/ui/uitext.h +++ b/src/display/ui/uitext.h @@ -7,16 +7,10 @@ #pragma once #include "asset/assetmanager.h" +#include "display/tileset/tileset_font_minogram.h" #define UI_TEXT_CHAR_START '!' -#define UI_TEXT_COLUMN_COUNT 16 -#define UI_TEXT_ROW_COUNT 6 -#define UI_TEXT_TILE_COUNT (UI_TEXT_COLUMN_COUNT*UI_TEXT_ROW_COUNT) - -#define UI_TEXT_TILE_WIDTH 6.0f -#define UI_TEXT_TILE_HEIGHT 10.0f - typedef struct { ref_t assetRef; asset_t *asset; diff --git a/tools/assetstool/processasset.py b/tools/assetstool/processasset.py index aecc0c2..4b5f1a2 100644 --- a/tools/assetstool/processasset.py +++ b/tools/assetstool/processasset.py @@ -3,6 +3,7 @@ import sys from processimage import processImage from processpalette import processPalette from processconfig import processConfig +from processtileset import processTileset processedAssets = [] @@ -20,8 +21,8 @@ def processAsset(asset): return processImage(asset) elif t == 'config': return processConfig(asset) - # elif t == 'tileset': - # return processTileset(asset) + elif t == 'tileset': + return processTileset(asset) else: print(f"Error: Unknown asset type '{asset['type']}' for path '{asset['path']}'") sys.exit(1) \ No newline at end of file diff --git a/tools/assetstool/processimage.py b/tools/assetstool/processimage.py index 904e312..7940c12 100644 --- a/tools/assetstool/processimage.py +++ b/tools/assetstool/processimage.py @@ -64,7 +64,10 @@ def processPalettizedImage(asset): f.write(data) outImage = { + "imagePath": outputFileRelative, "files": [ outputFilePath ], + 'width': image.width, + 'height': image.height, } return outImage @@ -90,6 +93,9 @@ def processAlphaImage(asset): f.write(data) outImage = { - "files": [ outputFilePath ] + "imagePath": outputFileRelative, + "files": [ outputFilePath ], + 'width': image.width, + 'height': image.height, } return outImage \ No newline at end of file diff --git a/tools/assetstool/processtileset.py b/tools/assetstool/processtileset.py new file mode 100644 index 0000000..5f5baca --- /dev/null +++ b/tools/assetstool/processtileset.py @@ -0,0 +1,84 @@ +import json +from processimage import processImage +import sys +from assethelpers import getAssetRelativePath +import os +import datetime +from args import args + +def processTileset(asset): + print(f"Processing tileset: {asset['path']}") + + # We need to determine how big each tile is. This can either be provided as + # an arg of tileWidth/tileHeight or as a count of rows/columns. + # Additionally, if the image has been factored, then the user can provide both + # tile sizes AND cols/rows to indicate the original size of the image. + + image = processImage(asset) + + tileWidth, tileHeight = None, None + columns, rows = None, None + originalWidth, originalHeight = image['width'], image['height'] + + if 'tileWidth' in asset['options'] and 'columns' in asset['options']: + tileWidth = int(asset['options']['tileWidth']) + columns = int(asset['options']['columns']) + originalWidth = tileWidth * columns + elif 'tileWidth' in asset['options']: + tileWidth = int(asset['options']['tileWidth']) + columns = image['width'] // tileWidth + elif 'columns' in asset['options']: + columns = int(asset['options']['columns']) + tileWidth = image['width'] // columns + else: + print(f"Error: Tileset {asset['path']} must specify either tileWidth or columns") + sys.exit(1) + + if 'tileHeight' in asset['options'] and 'rows' in asset['options']: + tileHeight = int(asset['options']['tileHeight']) + rows = int(asset['options']['rows']) + originalHeight = tileHeight * rows + elif 'tileHeight' in asset['options']: + tileHeight = int(asset['options']['tileHeight']) + rows = image['height'] // tileHeight + elif 'rows' in asset['options']: + rows = int(asset['options']['rows']) + tileHeight = image['height'] // rows + else: + print(f"Error: Tileset {asset['path']} must specify either tileHeight or rows") + sys.exit(1) + + fileNameWithoutExtension = os.path.splitext(os.path.basename(asset['path']))[0] + now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + tilesetName = fileNameWithoutExtension + tilesetNameUpper = tilesetName.upper() + + widthScale = originalWidth / image['width'] + heightScale = originalHeight / image['height'] + + # Create header + data = f"// Tileset Generated for {asset['path']} at {now}\n" + data += f"#pragma once\n" + data += f"#include \"display/tileset.h\"\n\n" + data += f"static const tileset_t TILESET_{tilesetNameUpper} = {{\n" + data += f" .tileWidth = {tileWidth},\n" + data += f" .tileHeight = {tileHeight},\n" + data += f" .tileCount = {columns * rows},\n" + data += f" .columns = {columns},\n" + data += f" .rows = {rows},\n" + data += f" .uv = {{ {widthScale / columns}f, {heightScale / rows}f }},\n" + data += f" .image = {json.dumps(image['imagePath'])},\n" + data += f"}};\n" + + # Write Header + outputFile = os.path.join(args.headers_dir, "display", "tileset", f"tileset_{tilesetName}.h") + os.makedirs(os.path.dirname(outputFile), exist_ok=True) + with open(outputFile, 'w') as f: + f.write(data) + + outTileset = { + "image": image, + "headerFile": outputFile, + "files": image['files'], + } + return outTileset \ No newline at end of file