diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index 9d910c8..e810f3d 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -3,5 +3,6 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT -add_asset(first.palette.png) -add_asset(entities.tsx) \ No newline at end of file +add_asset(PALETTE first.palette.png test0) +add_asset(TILESET entities.tsx test test3) +add_asset(IMAGE minogram_6x10.png test2) \ No newline at end of file diff --git a/assets/minogram_6x10.png b/assets/minogram_6x10.png new file mode 100644 index 0000000..a198a75 Binary files /dev/null and b/assets/minogram_6x10.png differ diff --git a/src/display/CMakeLists.txt b/src/display/CMakeLists.txt index ae46353..e0978ad 100644 --- a/src/display/CMakeLists.txt +++ b/src/display/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory(mesh) add_subdirectory(texture) add_subdirectory(scene) add_subdirectory(spritebatch) +add_subdirectory(ui) if(DUSK_TARGET_SYSTEM STREQUAL "linux") target_compile_definitions(${DUSK_TARGET_NAME} diff --git a/src/display/camera.c b/src/display/camera.c index c164966..cf5a5f5 100644 --- a/src/display/camera.c +++ b/src/display/camera.c @@ -10,32 +10,28 @@ #include "assert/assert.h" #include "display/framebuffer/framebuffer.h" -camera_t CAMERA_DATA[CAMERA_COUNT_MAX] = { 0 }; -camera_t *CAMERA_MAIN = NULL; - void cameraInit(camera_t *camera) { assertNotNull(camera, "Not a camera component"); - camera->type = CAMERA_TYPE_PERSPECTIVE; + camera->projType = CAMERA_PROJECTION_TYPE_PERSPECTIVE; camera->perspective.fov = glm_rad(45.0f); camera->nearClip = 0.1f; - camera->farClip = 100.0f; + camera->farClip = 1000.0f; - glm_lookat( - (vec3){ 5.0f, 5.0f, 5.0f }, - (vec3){ 0.0f, 0.0f, 0.0f }, - (vec3){ 0.0f, 1.0f, 0.0f }, - camera->transform - ); + camera->viewType = CAMERA_VIEW_TYPE_LOOKAT; + glm_vec3_copy((vec3){ 5.0f, 5.0f, 5.0f }, camera->lookat.position); + glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, camera->lookat.up); + glm_vec3_copy((vec3){ 0.0f, 0.0f, 0.0f }, camera->lookat.target); } void cameraPushMatrix(camera_t *camera) { assertNotNull(camera, "Not a camera component"); mat4 projection; + mat4 view; - switch(camera->type) { - case CAMERA_TYPE_ORTHOGRAPHIC: + switch(camera->projType) { + case CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC: glm_ortho( camera->orthographic.left, camera->orthographic.right, @@ -47,7 +43,7 @@ void cameraPushMatrix(camera_t *camera) { ); break; - case CAMERA_TYPE_PERSPECTIVE: + case CAMERA_PROJECTION_TYPE_PERSPECTIVE: const float_t aspect = ( (float_t)frameBufferGetWidth(FRAMEBUFFER_BOUND) / (float_t)frameBufferGetHeight(FRAMEBUFFER_BOUND) @@ -61,6 +57,21 @@ void cameraPushMatrix(camera_t *camera) { ); } + switch(camera->viewType) { + case CAMERA_VIEW_TYPE_MATRIX: + glm_mat4_copy(camera->view, view); + break; + + case CAMERA_VIEW_TYPE_LOOKAT: + glm_lookat( + camera->lookat.position, + camera->lookat.target, + camera->lookat.up, + view + ); + break; + } + #if DISPLAY_SDL2 // mat4 pv; // glm_mat4_mul(projection, camera->transform, pv); @@ -72,7 +83,7 @@ void cameraPushMatrix(camera_t *camera) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - glLoadMatrixf((const GLfloat*)camera->transform); + glLoadMatrixf((const GLfloat*)view); #endif } diff --git a/src/display/camera.h b/src/display/camera.h index a3f8f10..b3c86a4 100644 --- a/src/display/camera.h +++ b/src/display/camera.h @@ -12,14 +12,27 @@ #define CAMERA_COUNT_MAX 4 typedef enum { - CAMERA_TYPE_PERSPECTIVE, - CAMERA_TYPE_ORTHOGRAPHIC + CAMERA_PROJECTION_TYPE_PERSPECTIVE, + CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC } cameraprojectiontype_t; -typedef struct { - cameraprojectiontype_t type; +typedef enum { + CAMERA_VIEW_TYPE_MATRIX, + CAMERA_VIEW_TYPE_LOOKAT +} cameraviewtype_t; - mat4 transform; +typedef struct { + cameraprojectiontype_t projType; + cameraviewtype_t viewType; + + union { + mat4 view; + struct { + float_t position[3]; + float_t target[3]; + float_t up[3]; + } lookat; + }; union { struct { @@ -38,9 +51,6 @@ typedef struct { float_t farClip; } camera_t; -extern camera_t CAMERA_DATA[CAMERA_COUNT_MAX]; -extern camera_t *CAMERA_MAIN; - /** * Initializes a camera to default values. */ diff --git a/src/display/display.c b/src/display/display.c index 62c88f8..a1a32d7 100644 --- a/src/display/display.c +++ b/src/display/display.c @@ -86,8 +86,12 @@ errorret_t displayUpdate(void) { glViewport(0, 0, windowWidth, windowHeight); #endif - frameBufferBind(&FRAMEBUFFER_BACKBUFFER); spriteBatchClear(); + frameBufferBind(&FRAMEBUFFER_BACKBUFFER); + frameBufferClear( + FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, + COLOR_CORNFLOWER_BLUE + ); sceneManagerUpdate(); sceneManagerRender(); diff --git a/src/display/scene/overworld/sceneoverworld.c b/src/display/scene/overworld/sceneoverworld.c index 94d2a38..86ee46e 100644 --- a/src/display/scene/overworld/sceneoverworld.c +++ b/src/display/scene/overworld/sceneoverworld.c @@ -16,15 +16,7 @@ camera_t SCENE_OVERWORLD_CAMERA; void sceneOverworldInit(void) { cameraInit(&SCENE_OVERWORLD_CAMERA); - SCENE_OVERWORLD_CAMERA.type = CAMERA_TYPE_ORTHOGRAPHIC; - SCENE_OVERWORLD_CAMERA.orthographic.left = 0.0f; - SCENE_OVERWORLD_CAMERA.orthographic.right = frameBufferGetWidth(FRAMEBUFFER_BOUND); - SCENE_OVERWORLD_CAMERA.orthographic.top = 0.0f; - SCENE_OVERWORLD_CAMERA.orthographic.bottom = frameBufferGetHeight(FRAMEBUFFER_BOUND); - SCENE_OVERWORLD_CAMERA.nearClip = -1.0f; - SCENE_OVERWORLD_CAMERA.farClip = 100.0f; - - glm_mat4_identity(SCENE_OVERWORLD_CAMERA.transform); + glm_vec3_copy((vec3){32.0f, 32.0f, 32.0f}, SCENE_OVERWORLD_CAMERA.lookat.position); scene_t *scene = &SCENE_MANAGER_SCENES[SCENE_TYPE_OVERWORLD]; scene->flags |= SCENE_FLAG_ACTIVE | SCENE_FLAG_VISIBLE; @@ -34,17 +26,17 @@ void sceneOverworldUpdate(void) { } void sceneOverworldRender(void) { - frameBufferClear( - FRAMEBUFFER_CLEAR_COLOR | FRAMEBUFFER_CLEAR_DEPTH, - COLOR_CORNFLOWER_BLUE - ); - cameraPushMatrix(&SCENE_OVERWORLD_CAMERA); - meshDraw(&QUAD_MESH_SIMPLE, -1, -1); + + // Draw base layer + + // Draw entities + + // Draw overlay layer. spriteBatchPush( NULL, - 0.0f, 0.0f, 32.0f, 32.0f, + 0.0f, 0.0f, 12.0f, 12.0f, 0xFF, 0x00, 0x00, 0xFF, 0.0f, 0.0f, 1.0f, 1.0f ); diff --git a/src/display/ui/CMakeLists.txt b/src/display/ui/CMakeLists.txt new file mode 100644 index 0000000..7dd25c4 --- /dev/null +++ b/src/display/ui/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DUSK_TARGET_NAME} + PRIVATE + rendertext.c +) \ No newline at end of file diff --git a/src/display/ui/rendertext.c b/src/display/ui/rendertext.c new file mode 100644 index 0000000..41039f1 --- /dev/null +++ b/src/display/ui/rendertext.c @@ -0,0 +1,160 @@ +// /** +// * Copyright (c) 2025 Dominic Masters +// * +// * This software is released under the MIT License. +// * https://opensource.org/licenses/MIT +// */ + +// #include "rendertext.h" +// #include "display/display.h" +// #include "assert/assert.h" +// #include "display/spritebatch/spritebatch.h" +// #include "util/memory.h" +// #include "util/math.h" + +// texture_t RENDER_TEXT_TEXTURE; + +// static mesh_t RENDER_TEXT_QUAD_MESH; + +// void renderTextInit(void) { +// const int32_t cols = FONT_COLUMN_COUNT; +// const int32_t rows = (FONT_TILE_COUNT + cols - 1) / cols; +// const int32_t inputFontWidth = cols * FONT_TILE_WIDTH; +// const int32_t inputFontHeight = rows * FONT_TILE_HEIGHT; + +// int32_t outputFontWidth = inputFontWidth; +// int32_t outputFontHeight = inputFontHeight; + +// // // Round up to nearest power of 2 +// // #if PSP +// // outputFontWidth = mathNextPowTwo(inputFontWidth); +// // outputFontHeight = mathNextPowTwo(inputFontHeight); +// // #endif + +// uint8_t *pixels = (uint8_t *)memoryAllocate( +// outputFontWidth * outputFontHeight * +// sizeof(uint8_t) +// ); + +// // Buffer the pixels. +// for(int tileIndex = 0; tileIndex < FONT_TILE_COUNT; ++tileIndex) { +// const int32_t tileX = (tileIndex % FONT_COLUMN_COUNT) * FONT_TILE_WIDTH; +// const int32_t tileY = (tileIndex / FONT_COLUMN_COUNT) * FONT_TILE_HEIGHT; +// const uint8_t* tile = TILE_PIXEL_DATA[tileIndex]; + +// for (int y = 0; y < FONT_TILE_HEIGHT; ++y) { +// for (int x = 0; x < FONT_TILE_WIDTH; ++x) { +// const int32_t pixel = (tileY + y) * outputFontWidth + (tileX + x); +// const int32_t pixelOffset = pixel; +// uint8_t value = tile[y * FONT_TILE_WIDTH + x]; +// pixels[pixel] = value ? 0xFF : 0x00; // Alpha channel +// } +// } +// } + +// textureInit( +// &RENDER_TEXT_TEXTURE, +// outputFontWidth, outputFontHeight, +// TEXTURE_FORMAT_ALPHA, pixels +// ); +// memoryFree(pixels); +// } + +// void renderTextDrawChar( +// const float_t x, +// const float_t y, +// const char_t c, +// const uint8_t r, +// const uint8_t g, +// const uint8_t b +// ) { +// int32_t tileIndex = (int32_t)(c) - FONT_CHAR_START; +// assertTrue( +// tileIndex >= 0 && tileIndex < FONT_TILE_COUNT, +// "Character is out of bounds for font tiles" +// ); + +// const float_t w = (float)RENDER_TEXT_TEXTURE.width; +// const float_t h = (float)RENDER_TEXT_TEXTURE.height; +// const int32_t tileX = (tileIndex % FONT_COLUMN_COUNT); +// const int32_t tileY = (tileIndex / FONT_COLUMN_COUNT); + +// spriteBatchPush( +// &RENDER_TEXT_TEXTURE, +// x, y, +// x + FONT_TILE_WIDTH, y + FONT_TILE_HEIGHT, +// r, g, b, 0xFF, +// (tileX * FONT_TILE_WIDTH) / w, +// (tileY * FONT_TILE_HEIGHT) / h, +// ((tileX + 1) * FONT_TILE_WIDTH) / w, +// ((tileY + 1) * FONT_TILE_HEIGHT) / h +// ); +// } + +// void renderTextDraw( +// const float_t x, +// const float_t y, +// const char_t *text, +// const uint8_t r, +// const uint8_t g, +// const uint8_t b +// ) { +// assertNotNull(text, "Text cannot be NULL"); + +// float_t posX = x; +// float_t posY = y; + +// char_t c; +// int32_t i = 0; +// while((c = text[i++]) != '\0') { +// if(c == '\n') { +// posX = x; +// posY += FONT_TILE_HEIGHT; +// continue; +// } + +// renderTextDrawChar(posX, posY, c, r, g, b); +// posX += FONT_TILE_WIDTH; +// } +// } + +// void renderTextMeasure( +// const char_t *text, +// int32_t *outWidth, +// int32_t *outHeight +// ) { +// assertNotNull(text, "Text cannot be NULL"); +// assertNotNull(outWidth, "Output width pointer cannot be NULL"); +// assertNotNull(outHeight, "Output height pointer cannot be NULL"); + +// int32_t width = 0; +// int32_t height = FONT_TILE_HEIGHT; +// int32_t lineWidth = 0; + +// char_t c; +// int32_t i = 0; +// while((c = text[i++]) != '\0') { +// if(c == '\n') { +// if(lineWidth > width) { +// width = lineWidth; +// } +// lineWidth = 0; +// height += FONT_TILE_HEIGHT; +// continue; +// } + +// lineWidth += FONT_TILE_WIDTH; +// } + +// if(lineWidth > width) { +// width = lineWidth; +// } + +// *outWidth = width; +// *outHeight = height; +// } +// lineWidth += FONT_TILE_WIDTH; + +// void renderTextDispose(void) { +// textureDispose(&RENDER_TEXT_TEXTURE); +// } \ No newline at end of file diff --git a/src/display/ui/rendertext.h b/src/display/ui/rendertext.h new file mode 100644 index 0000000..272de6a --- /dev/null +++ b/src/display/ui/rendertext.h @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "display/texture/texture.h" + +extern texture_t RENDER_TEXT_TEXTURE; + +/** + * Initializes the text rendering system. + */ +void renderTextInit(void); + +/** + * Draws a single character at the specified position. + * + * @param x The x-coordinate to draw the character at. + * @param y The y-coordinate to draw the character at. + * @param c The character to draw. + * @param r The red component of the color (0-255). + * @param g The green component of the color (0-255). + * @param b The blue component of the color (0-255). + */ +void renderTextDrawChar( + const float_t x, + const float_t y, + const char_t c, + const uint8_t r, + const uint8_t g, + const uint8_t b +); + +/** + * Draws a string of text at the specified position. + * + * @param x The x-coordinate to draw the text at. + * @param y The y-coordinate to draw the text at. + * @param text The null-terminated string of text to draw. + * @param r The red component of the color (0-255). + * @param g The green component of the color (0-255). + * @param b The blue component of the color (0-255). + */ +void renderTextDraw( + const float_t x, + const float_t y, + const char_t *text, + const uint8_t r, + const uint8_t g, + const uint8_t b +); + +/** + * Measures the width and height of the given text string when rendered. + * + * @param text The null-terminated string of text to measure. + * @param outWidth Pointer to store the measured width in pixels. + * @param outHeight Pointer to store the measured height in pixels. + */ +void renderTextMeasure( + const char_t *text, + int32_t *outWidth, + int32_t *outHeight +); + +/** + * Disposes of the text rendering system, freeing any allocated resources. + */ +void renderTextDispose(void); \ No newline at end of file diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 75b1187..3782c3a 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -9,4 +9,5 @@ target_sources(${DUSK_TARGET_NAME} memory.c string.c math.c + reflist.c ) \ No newline at end of file diff --git a/src/util/reflist.c b/src/util/reflist.c new file mode 100644 index 0000000..2d20a85 --- /dev/null +++ b/src/util/reflist.c @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "reflist.h" +#include "util/memory.h" +#include "assert/assert.h" + +void refListInit( + reflist_t *list, + ref_t *array, + const uint_fast16_t max +) { + assertNotNull(list, "Reference list cannot be NULL"); + assertNotNull(array, "Reference array cannot be NULL"); + assertTrue(max > 0, "Reference list max must be greater than 0"); + assertTrue(max <= UINT16_MAX, "Reference list too large"); + + memoryZero(list, sizeof(reflist_t)); + memoryZero(array, sizeof(ref_t) * max); + + list->array = array; + list->max = max; +} + +ref_t refListLock(reflist_t *list) { + assertFalse(refListIsFull(list), "Reference list is full"); + + ref_t ref = list->refNext++; + assertTrue(ref > 0, "Reference ID overflow"); + assertTrue(ref < UINT16_MAX, "Reference ID too large."); + + bool_t empty = list->onNotEmpty && refListIsEmpty(list); + + list->array[list->count++] = ref; + + if(empty) list->onNotEmpty(list); + if(list->onAdd) list->onAdd(list, ref); + if(list->onFull && refListIsFull(list)) list->onFull(list); + + return ref; +} + +void refListUnlock(reflist_t *list, const ref_t ref) { + assertFalse(refListIsEmpty(list), "Reference list is empty"); + + ref_t *slot = list->array; + ref_t *end = list->array + list->count; + + do { + if(*slot == ref) break; + ++slot; + } while(slot < end); + + assertTrue(slot < end, "Reference not found in list"); + + memoryMove(slot, slot + 1, (end - slot - 1) * sizeof(ref_t)); + list->count--; + + if(list->onRemove) list->onRemove(list, ref); + if(list->onEmpty && refListIsEmpty(list)) list->onEmpty(list); +} + +bool_t refListIsFull(const reflist_t *list) { + assertNotNull(list, "Reference list cannot be NULL"); + assertNotNull(list->array, "Reference list array cannot be NULL"); + assertTrue(list->max > 0, "Reference list max must be greater than 0"); + assertTrue(list->max <= UINT16_MAX, "Reference list too large"); + + return (list->count >= list->max); +} + +bool_t refListIsEmpty(const reflist_t *list) { + assertNotNull(list, "Reference list cannot be NULL"); + assertNotNull(list->array, "Reference list array cannot be NULL"); + assertTrue(list->max > 0, "Reference list max must be greater than 0"); + assertTrue(list->max <= UINT16_MAX, "Reference list too large"); + + return (list->count == 0); +} \ No newline at end of file diff --git a/src/util/reflist.h b/src/util/reflist.h new file mode 100644 index 0000000..824e21b --- /dev/null +++ b/src/util/reflist.h @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef uint_fast16_t ref_t; + +typedef struct reflist_s reflist_t; + +typedef struct reflist_s { + ref_t *array; + uint_fast16_t max; + uint_fast16_t count; + ref_t refNext; + + void *user; + + void (*onNotEmpty)(reflist_t *list); + void (*onFull)(reflist_t *list); + void (*onAdd)(reflist_t *list, const ref_t ref); + void (*onRemove)(reflist_t *list, const ref_t ref); + void (*onEmpty)(reflist_t *list); +} reflist_t; + +/** + * Initialize a reference list. Reference lists just hold a list of references + * that are "holding on to" a given object/resource. + * + * @param list The reference list to initialize. + * @param array The array to use as backing storage for the list. + * @param max The maximum number of references the list can hold. + */ +void refListInit( + reflist_t *list, + ref_t *array, + const uint_fast16_t max +); + + +/** + * Lock a reference in the list. This will return a new reference ID that can be + * used to access the locked reference. + * + * @param list The reference list to lock a reference in. + * @return The locked reference ID, or 0 if the list is full. + */ +ref_t refListLock(reflist_t *list); + +/** + * Unlock a reference in the list. This will free up the reference ID for + * reuse. + * + * @param list The reference list to unlock a reference in. + * @param ref The reference ID to unlock. + */ +void refListUnlock(reflist_t *list, const ref_t ref); + +/** + * Checks if the reference list is full. + * + * @param list The reference list to check. + * @return true if the list is full, false otherwise. + */ +bool_t refListIsFull(const reflist_t *list); + +/** + * Checks if the reference list is empty. + * + * @param list The reference list to check. + * @return true if the list is empty, false otherwise. + */ +bool_t refListIsEmpty(const reflist_t *list); \ No newline at end of file diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 6524e7f..5cb155a 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -4,7 +4,10 @@ # https://opensource.org/licenses/MIT # Function that adds an asset to be compiled -function(add_asset ASSET_PATH) +function(add_asset ASSET_TYPE ASSET_PATH) + message(STATUS "Adding asset: ${ASSET_PATH} (type: ${ASSET_TYPE})") + message(STATUS " Options: ${ARGN}") + set(FULL_ASSET_PATH "${CMAKE_CURRENT_LIST_DIR}/${ASSET_PATH}") list(APPEND DUSK_ASSETS ${FULL_ASSET_PATH}) set(DUSK_ASSETS ${DUSK_ASSETS} CACHE INTERNAL ${DUSK_CACHE_TARGET})