diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c48e55..70e8aac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ set(DUSK_TEMP_DIR "${DUSK_BUILD_DIR}/temp") set(DUSK_TOOLS_DIR "${DUSK_ROOT_DIR}/tools") set(DUSK_DATA_DIR "${DUSK_ROOT_DIR}/data") set(DUSK_ASSETS_DIR "${DUSK_ROOT_DIR}/assets") -set(DUSK_BUILT_ASSETS_DIR "${DUSK_BUILD_DIR}/assets" CACHE INTERNAL ${DUSK_CACHE_TARGET}) +set(DUSK_BUILT_ASSETS_DIR "${DUSK_BUILD_DIR}/built_assets" CACHE INTERNAL ${DUSK_CACHE_TARGET}) set(DUSK_GENERATED_HEADERS_DIR "${DUSK_BUILD_DIR}/generated") set(DUSK_TARGET_NAME "Dusk" CACHE INTERNAL ${DUSK_CACHE_TARGET}) set(DUSK_BUILD_BINARY ${DUSK_BUILD_DIR}/Dusk CACHE INTERNAL ${DUSK_CACHE_TARGET}) diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index 2cdb0ba..96118a1 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -3,4 +3,6 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -add_asset(entities.tsx) \ No newline at end of file +add_asset(test.palette.png) +# add_asset(first.palette.png) +# add_asset(entities.tsx) \ No newline at end of file diff --git a/assets/first.palette.png b/assets/first.palette.png new file mode 100644 index 0000000..fc473aa Binary files /dev/null and b/assets/first.palette.png differ diff --git a/assets/test.palette.png b/assets/test.palette.png new file mode 100644 index 0000000..aa512b7 Binary files /dev/null and b/assets/test.palette.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f45e676..07ee834 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,6 +10,7 @@ target_link_libraries(${DUSK_TARGET_NAME} PUBLIC m cglm + zip ) # Includes diff --git a/src/asset/CMakeLists.txt b/src/asset/CMakeLists.txt index afa05a7..364c0b2 100644 --- a/src/asset/CMakeLists.txt +++ b/src/asset/CMakeLists.txt @@ -8,4 +8,10 @@ target_sources(${DUSK_TARGET_NAME} PRIVATE asset.c assetsystem.c +) + +# Compile definitions +target_compile_definitions(${DUSK_TARGET_NAME} + PRIVATE + ASSET_TYPE=wad ) \ No newline at end of file diff --git a/src/asset/asset.h b/src/asset/asset.h index 0bf2138..9e82d59 100644 --- a/src/asset/asset.h +++ b/src/asset/asset.h @@ -7,11 +7,19 @@ #pragma once #include "dusk.h" +#include + +#define ASSET_FILENAME_MAX 256 typedef struct asset_s { int32_t nothing; + + const char_t *filename; + zip_file_t *fileHandle; } asset_t; -void assetInit(void); +void assetInit( + const char_t *filename, +); void assetDispose(void); \ No newline at end of file diff --git a/src/asset/assetimage.h b/src/asset/assetpalette.h similarity index 51% rename from src/asset/assetimage.h rename to src/asset/assetpalette.h index 43ad2ef..88f0ddc 100644 --- a/src/asset/assetimage.h +++ b/src/asset/assetpalette.h @@ -7,8 +7,12 @@ #pragma once #include "dusk.h" +#include "display/color.h" + +#define ASSET_PALETTE_COLOR_COUNT_MAX 256 typedef struct { - const int32_t width; - const int32_t height; -} assetimage_t; \ No newline at end of file + char_t header[3]; + int32_t colorCount; + color4b_t colors[ASSET_PALETTE_COLOR_COUNT_MAX]; +} assetpalette_t; \ No newline at end of file diff --git a/src/asset/assetsystem.c b/src/asset/assetsystem.c index 846823b..32900ae 100644 --- a/src/asset/assetsystem.c +++ b/src/asset/assetsystem.c @@ -7,14 +7,68 @@ #include "assetsystem.h" #include "util/memory.h" +#include "console/console.h" + +#define ASSET_SYSTEM_ASSET_FILE "dusk.dsk" + +const char_t ASSET_SYSTEM_SEARCH_PATHS[][FILENAME_MAX] = { + "%s/%s", + "./%s", + "../%s", + "../../%s", + "data/%s", + "../data/%s", +}; +#define ASSET_SYSTEM_SEARCH_PATHS_COUNT (\ + sizeof(ASSET_SYSTEM_SEARCH_PATHS) / FILENAME_MAX\ +) assetsystem_t ASSET_SYSTEM; -void assetSystemInit(void) { +errorret_t assetSystemInit(void) { memoryZero(&ASSET_SYSTEM, sizeof(assetsystem_t)); - // threadInit(&ASSET_SYSTEM.thread, NULL); + + // Open zip file + char_t searchPath[FILENAME_MAX]; + consolevar_t *var = consoleVarGet("sys_path"); + const char_t *sysPath = var ? var->value : "."; + for(int32_t i = 0; i < ASSET_SYSTEM_SEARCH_PATHS_COUNT; i++) { + sprintf( + searchPath, + ASSET_SYSTEM_SEARCH_PATHS[i], + sysPath, + ASSET_SYSTEM_ASSET_FILE + ); + + // Try open + ASSET_SYSTEM.zip = zip_open(searchPath, ZIP_RDONLY, NULL); + if(ASSET_SYSTEM.zip == NULL) continue; + consolePrint("Opened asset file: %s", searchPath); + break; + } + + // Did we open the asset? + if(ASSET_SYSTEM.zip == NULL) errorThrow("Failed to open asset file."); + + // Get "test.palette.dpf" file. + zip_file_t *file = zip_fopen(ASSET_SYSTEM.zip, "test.palette.dpf", 0); + if(file == NULL) errorThrow("Failed to open test.palette.dpf in asset file."); + + // Read it + char_t buffer[256]; + zip_int64_t n = zip_fread(file, buffer, 256); + if(n < 0) { + zip_fclose(file); + errorThrow("Failed to read test.palette.dpf in asset file."); + } + + + errorOk(); } void assetSystemDispose(void) { - // threadDispose(&ASSET_SYSTEM.thread); + if(ASSET_SYSTEM.zip != NULL) { + zip_close(ASSET_SYSTEM.zip); + ASSET_SYSTEM.zip = NULL; + } } \ No newline at end of file diff --git a/src/asset/assetsystem.h b/src/asset/assetsystem.h index 14efe0e..aee3566 100644 --- a/src/asset/assetsystem.h +++ b/src/asset/assetsystem.h @@ -7,9 +7,16 @@ #pragma once #include "asset.h" +#include "error/error.h" + +#if ASSET_TYPE == wad +#else + #error "Unsupported ASSET_TYPE" +#endif typedef struct { int32_t nothing; + zip_t *zip; } assetsystem_t; extern assetsystem_t ASSET_SYSTEM; @@ -17,7 +24,7 @@ extern assetsystem_t ASSET_SYSTEM; /** * Initializes the asset system. */ -void assetSystemInit(void); +errorret_t assetSystemInit(void); /** * Disposes/cleans up the asset system. diff --git a/src/asset/assettileset.h b/src/asset/assettileset.h index fbdb261..8d51439 100644 --- a/src/asset/assettileset.h +++ b/src/asset/assettileset.h @@ -11,9 +11,10 @@ typedef struct assetimage_s assetimage_t; typedef struct { - const int32_t tileWidth; - const int32_t tileHeight; - const int32_t tileCount; - const int32_t columns; - const assetimage_t *image; + char_t header[3]; + int32_t tileWidth; + int32_t tileHeight; + int32_t tileCount; + int32_t columns; + char_t tilesetName[256]; } assettileset_t; \ No newline at end of file diff --git a/src/console/CMakeLists.txt b/src/console/CMakeLists.txt index 4a436a7..dbcfa14 100644 --- a/src/console/CMakeLists.txt +++ b/src/console/CMakeLists.txt @@ -18,6 +18,6 @@ add_subdirectory(cmd) if(DUSK_TARGET_SYSTEM STREQUAL "linux") target_compile_definitions(${DUSK_TARGET_NAME} PRIVATE - DUSK_CONSOLE_POSIX=1 + CONSOLE_POSIX=1 ) endif() \ No newline at end of file diff --git a/src/console/console.c b/src/console/console.c index 99576a9..d41deee 100644 --- a/src/console/console.c +++ b/src/console/console.c @@ -45,11 +45,32 @@ consolevar_t * consoleRegVar( const char_t *value, consolevarchanged_t event ) { - consolevar_t *var = &CONSOLE.variables[CONSOLE.variableCount++]; + consolevar_t *var; + + // Existing? + var = consoleVarGet(name); + if(var != NULL) return var; + + assertTrue( + CONSOLE.variableCount < CONSOLE_VARIABLES_MAX, + "Too many console variables registered." + ); + + // Create + var = &CONSOLE.variables[CONSOLE.variableCount++]; consoleVarInitListener(var, name, value, event); return var; } +consolevar_t * consoleVarGet(const char_t *name) { + assertNotNull(name, "name must not be NULL"); + for(uint32_t i = 0; i < CONSOLE.variableCount; i++) { + consolevar_t *var = &CONSOLE.variables[i]; + if(stringCompare(var->name, name) == 0) return var; + } + return NULL; +} + void consolePrint(const char_t *message, ...) { char_t buffer[CONSOLE_LINE_MAX]; diff --git a/src/console/console.h b/src/console/console.h index 2653ecd..64d9462 100644 --- a/src/console/console.h +++ b/src/console/console.h @@ -85,6 +85,14 @@ consolevar_t * consoleRegVar( consolevarchanged_t event ); +/** + * Gets a console variable by name. + * + * @param name The name of the variable. + * @return The variable, or NULL if not found. + */ +consolevar_t * consoleVarGet(const char_t *name); + /** * Sets the value of a console variable. * diff --git a/src/engine/engine.c b/src/engine/engine.c index 915c67a..887857c 100644 --- a/src/engine/engine.c +++ b/src/engine/engine.c @@ -26,7 +26,7 @@ errorret_t engineInit(void) { timeInit(); consoleInit(); ecsSystemInit(); - assetSystemInit(); + errorChain(assetSystemInit()); errorChain(displayInit()); sceneTestAdd(); diff --git a/src/error/error.h b/src/error/error.h index c83c7df..65c202d 100644 --- a/src/error/error.h +++ b/src/error/error.h @@ -115,7 +115,7 @@ errorret_t errorPrint(const errorret_t retval); #define errorThrow(message, ...) \ return errorThrowImpl(\ &ERROR_STATE, ERROR_NOT_OK, __FILE__, __func__, __LINE__, (message), \ - __VA_ARGS__ \ + ##__VA_ARGS__ \ ) /** diff --git a/src/main.c b/src/main.c index e710323..0aa8028 100644 --- a/src/main.c +++ b/src/main.c @@ -6,6 +6,7 @@ */ #include "engine/engine.h" +#include "console/console.h" // PSP_MODULE_INFO("Dusk", 0, 1, 0); // PSP_MAIN_THREAD_ATTR(PSP_THREAD_ATTR_USER | THREAD_ATTR_VFPU); @@ -14,6 +15,9 @@ int main(int argc, char **argv) { errorret_t ret; ret = engineInit(); + // Set console variable + if(argc > 0) consoleRegVar("sys_path", argv[0], NULL); + if(ret.code != ERROR_OK) { errorCatch(errorPrint(ret)); return ret.code; diff --git a/tools/assetstool/assethelpers.py b/tools/assetstool/assethelpers.py index a9060c3..8ae56c9 100644 --- a/tools/assetstool/assethelpers.py +++ b/tools/assetstool/assethelpers.py @@ -4,3 +4,7 @@ from args import args def getAssetRelativePath(fullPath): # Get the relative path to the asset return os.path.relpath(fullPath, start=args.assets).replace('\\', '/') + +def getBuiltAssetsRelativePath(fullPath): + # Get the relative path to the built asset + return os.path.relpath(fullPath, start=args.output_assets).replace('\\', '/') \ No newline at end of file diff --git a/tools/assetstool/assets.py b/tools/assetstool/assets.py index 95ba826..f1fc386 100644 --- a/tools/assetstool/assets.py +++ b/tools/assetstool/assets.py @@ -1,6 +1,8 @@ import sys, os -from args import inputAssets +from args import inputAssets, args from processasset import processAsset +from assethelpers import getBuiltAssetsRelativePath +import zipfile # Setup headers directory. # setOutputDir(args.output) @@ -10,5 +12,29 @@ from processasset import processAsset # if not os.path.exists(args.output): # os.makedirs(args.output) +files = [] + for asset in inputAssets: - processAsset(asset) \ No newline at end of file + asset = processAsset(asset) + files.extend(asset['files']) + +# Take assets and add to a zip archive. +outputFileName = args.output_file +print(f"Creating output file: {outputFileName}") +with zipfile.ZipFile(outputFileName, 'w') as zipf: + for file in files: + relativeOutputPath = getBuiltAssetsRelativePath(file) + zipf.write(file, arcname=relativeOutputPath) + +# Finalize build +if args.build_type == 'header': + print("Error: Header build not implemented yet.") + sys.exit(1) + +elif args.build_type == 'wad': + # Nothing to do, already created above! + pass + +else: + print("Error: Unknown build type.") + sys.exit(1) \ No newline at end of file diff --git a/tools/assetstool/constants.py b/tools/assetstool/constants.py new file mode 100644 index 0000000..4537adb --- /dev/null +++ b/tools/assetstool/constants.py @@ -0,0 +1 @@ +ASSET_FILE_NAME_MAX_LENGTH = 256 \ No newline at end of file diff --git a/tools/assetstool/processasset.py b/tools/assetstool/processasset.py index eb53bc6..032e8e0 100644 --- a/tools/assetstool/processasset.py +++ b/tools/assetstool/processasset.py @@ -1,4 +1,5 @@ from processtileset import processTileset +from processimage import processPalette, processImage processedAssets = [] @@ -10,5 +11,9 @@ def processAsset(assetPath): # Handle tiled tilesets if assetPath.endswith('.tsx'): - processTileset(assetPath) - return \ No newline at end of file + return processTileset(assetPath) + elif assetPath.endswith('.png'): + if assetPath.endswith('.palette.png'): + return processPalette(assetPath) + else: + return processImage(assetPath) \ No newline at end of file diff --git a/tools/assetstool/processimage.py b/tools/assetstool/processimage.py new file mode 100644 index 0000000..5a3fbfa --- /dev/null +++ b/tools/assetstool/processimage.py @@ -0,0 +1,64 @@ +import os +from args import args +from PIL import Image + +def extractPaletteFromImage(image): + # goes through and finds all unique colors in the image + if image.mode != 'RGBA': + image = image.convert('RGBA') + pixels = list(image.getdata()) + uniqueColors = [] + for color in pixels: + if color not in uniqueColors: + uniqueColors.append(color) + return uniqueColors + +def savePalette(pixels, outputFilePath): + # Pixels is a list of (R, G, B, A) tuples + data = "DPF" # Header for Dusk Palette Format + + # Count of colors (int32_t) + colorCount = len(pixels) + data += colorCount.to_bytes(4, byteorder='little').decode('latin1') + + for r, g, b, a in pixels: + data += bytes([r, g, b, a]).decode('latin1') + + os.makedirs(os.path.dirname(outputFilePath), exist_ok=True) + with open(outputFilePath, 'wb') as f: + f.write(data.encode('latin1')) + +def processPalette(assetPath): + # Process the image file + print(f"Processing palette: {assetPath}") + + # Load the image + image = Image.open(assetPath) + pixels = extractPaletteFromImage(image) + + # Save the processed image to the output directory + fileNameWithoutExt = os.path.splitext(os.path.basename(assetPath))[0] + outputFilePath = os.path.join(args.output_assets, f"{fileNameWithoutExt}.dpf") + os.makedirs(os.path.dirname(outputFilePath), exist_ok=True) + savePalette(pixels, outputFilePath) + + outputRelative = os.path.relpath(outputFilePath, args.output_assets) + + return { + "outputFile": outputRelative, + "paletteColors": len(pixels), + "files": [ outputFilePath ] + } + +def processImage(assetPath): + print(f"Processing image: {assetPath}") + + # Load the image + image = Image.open(assetPath) + pixels = extractPaletteFromImage(image) + + return { + # "outputFile": os.path.relpath(assetPath, args.input_assets), + # "paletteColors": len(pixels), + "files": [ assetPath ] + } \ No newline at end of file diff --git a/tools/assetstool/processtileset.py b/tools/assetstool/processtileset.py index baad0ea..2e5db01 100644 --- a/tools/assetstool/processtileset.py +++ b/tools/assetstool/processtileset.py @@ -1,6 +1,9 @@ import sys, os import xml.etree.ElementTree as ET from assethelpers import getAssetRelativePath +from args import args +from constants import ASSET_FILE_NAME_MAX_LENGTH +from processimage import processPalette, processImage def processTileset(assetPath): # Process the tileset file @@ -25,18 +28,58 @@ def processTileset(assetPath): return # Exactly one image element is required - images = root.findall('image') - if len(images) != 1: + imagesNode = root.findall('image') + if len(imagesNode) != 1: print(f"Error: Tileset {assetPath} must have exactly one image element.") return - image = images[0] - if 'source' not in image.attrib: + imageNode = imagesNode[0] + if 'source' not in imageNode.attrib: print(f"Error: Tileset {assetPath} is missing image source.") return - - imageSource = image.attrib['source'] + + imageSource = imageNode.attrib['source'] + + directory = os.path.dirname(assetPath) + image = processImage(os.path.join(directory, imageSource)) # Build - relative = getAssetRelativePath(assetPath) - print(f"Relative path: {relative}") \ No newline at end of file + relativeFile = getAssetRelativePath(assetPath) + relativeDir = os.path.dirname(relativeFile) + + data = "DTF" # Header for Dusk Tileset Format + # Write width (int32_t) + data += tilewidth.to_bytes(4, byteorder='little').decode('latin1') + # Write height (int32_t) + data += tileheight.to_bytes(4, byteorder='little').decode('latin1') + # Write tilecount (int32_t) + data += tilecount.to_bytes(4, byteorder='little').decode('latin1') + # Write column count (int32_t) + data += columns.to_bytes(4, byteorder='little').decode('latin1') + # Write row count (int32_t) + rows = (tilecount + columns - 1) // columns + data += rows.to_bytes(4, byteorder='little').decode('latin1') + + # Write image source file name, padd to ASSET_FILE_NAME_MAX_LENGTH + imageSourceBytes = image["outputFile"].encode('utf-8') + data += len(imageSourceBytes).to_bytes(4, byteorder='little').decode('latin1') + data += imageSourceBytes.decode('latin1') + paddingLength = max(0, ASSET_FILE_NAME_MAX_LENGTH - len(imageSourceBytes)) + data += '\x00' * paddingLength + + # Write to output file + fileNameWithoutExt = os.path.splitext(os.path.basename(assetPath))[0] + outputFilePath = os.path.join(args.output_assets, relativeDir, f"{fileNameWithoutExt}.dtf") + os.makedirs(os.path.dirname(outputFilePath), exist_ok=True) + with open(outputFilePath, 'wb') as f: + f.write(data.encode('latin1')) + + return { + "outputFile": os.path.relpath(outputFilePath, args.output_assets), + "tileWidth": tilewidth, + "tileHeight": tileheight, + "tileCount": tilecount, + "columns": columns, + "rows": rows, + "image": image + } \ No newline at end of file