diff --git a/src/dusk/asset/CMakeLists.txt b/src/dusk/asset/CMakeLists.txt index 39e32cab..6dff1022 100644 --- a/src/dusk/asset/CMakeLists.txt +++ b/src/dusk/asset/CMakeLists.txt @@ -8,6 +8,8 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} PUBLIC asset.c assetfile.c + assetcache.c + assetbatch.c ) # Subdirs diff --git a/src/dusk/asset/asset.c b/src/dusk/asset/asset.c index 78996bb6..729d95d6 100644 --- a/src/dusk/asset/asset.c +++ b/src/dusk/asset/asset.c @@ -16,11 +16,12 @@ asset_t ASSET; errorret_t assetInit(void) { memoryZero(&ASSET, sizeof(asset_t)); - + // assetInitPlatform must either define ASSET.zip or throw an error. errorChain(assetInitPlatform()); assertNotNull(ASSET.zip, "Asset zip null without error."); + assetCacheInit(&ASSET.cache); errorOk(); } @@ -50,13 +51,15 @@ errorret_t assetLoad( } errorret_t assetDispose(void) { + assetCacheDispose(&ASSET.cache); + if(ASSET.zip != NULL) { if(zip_close(ASSET.zip) != 0) { errorThrow("Failed to close asset zip archive."); } ASSET.zip = NULL; } - + errorChain(assetDisposePlatform()); errorOk(); } \ No newline at end of file diff --git a/src/dusk/asset/asset.h b/src/dusk/asset/asset.h index fb25bced..cc880abe 100644 --- a/src/dusk/asset/asset.h +++ b/src/dusk/asset/asset.h @@ -9,6 +9,7 @@ #include "error/error.h" #include "asset/assetplatform.h" #include "assetfile.h" +#include "assetcache.h" #ifndef assetInitPlatform #error "Platform must define assetInitPlatform function." @@ -23,6 +24,7 @@ typedef struct asset_s { zip_t *zip; assetplatform_t platform; + assetcache_t cache; } asset_t; extern asset_t ASSET; diff --git a/src/dusk/asset/assetbatch.c b/src/dusk/asset/assetbatch.c new file mode 100644 index 00000000..bf22601e --- /dev/null +++ b/src/dusk/asset/assetbatch.c @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "assetbatch.h" +#include "asset.h" +#include "util/memory.h" +#include "util/string.h" +#include "assert/assert.h" + +void assetBatchInit( + assetbatch_t *batch, + assetbatchcomplete_t onComplete, + assetbatcherror_t onError, + void *context +) { + memoryZero(batch, sizeof(assetbatch_t)); + batch->onComplete = onComplete; + batch->onError = onError; + batch->context = context; +} + +void assetBatchAdd( + assetbatch_t *batch, + const char_t *path, + assetfileloader_t loader, + const void *params, + size_t paramsSize, + size_t dataSize, + assetcachedisposer_t disposer, + void **outPtr +) { + assertTrue(batch->count < ASSET_BATCH_MAX, "Asset batch is full."); + assertTrue(paramsSize <= ASSET_BATCH_PARAMS_SIZE, "Asset batch params too large."); + assertNotNull(outPtr, "Batch output pointer cannot be NULL."); + + assetbatchitem_t *item = &batch->items[batch->count++]; + stringCopy(item->path, path, ASSET_FILE_NAME_MAX); + item->loader = loader; + if (params != NULL && paramsSize > 0) { + memoryCopy(item->params, params, paramsSize); + } + item->dataSize = dataSize; + item->disposer = disposer; + item->outPtr = outPtr; +} + +errorret_t assetBatchLoad(assetbatch_t *batch) { + for (uint8_t i = 0; i < batch->count; i++) { + assetbatchitem_t *item = &batch->items[i]; + + void *cached = assetCacheLookup(&ASSET.cache, item->path); + if (cached != NULL) { + assetCacheRetain(&ASSET.cache, item->path); + *item->outPtr = cached; + continue; + } + + void *data = memoryAllocate(item->dataSize); + errorret_t err = assetLoad(item->path, item->loader, item->params, data); + if (err.code != ERROR_OK) { + memoryFree(data); + if (batch->onError != NULL) { + batch->onError(batch, batch->context, err); + } + return errorChainImpl(err, __FILE__, __func__, __LINE__); + } + + assetCacheInsert(&ASSET.cache, item->path, data, item->disposer); + *item->outPtr = data; + } + + if (batch->onComplete != NULL) { + batch->onComplete(batch, batch->context); + } + + errorOk(); +} diff --git a/src/dusk/asset/assetbatch.h b/src/dusk/asset/assetbatch.h new file mode 100644 index 00000000..7e6a1a3e --- /dev/null +++ b/src/dusk/asset/assetbatch.h @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "assetcache.h" +#include "error/error.h" + +#ifndef ASSET_BATCH_MAX + #define ASSET_BATCH_MAX 16 +#endif + +#define ASSET_BATCH_PARAMS_SIZE 16 + +typedef struct assetbatch_s assetbatch_t; + +typedef void (*assetbatchcomplete_t)(assetbatch_t *batch, void *context); +typedef void (*assetbatcherror_t)( + assetbatch_t *batch, + void *context, + errorret_t error +); + +typedef struct { + char_t path[ASSET_FILE_NAME_MAX]; + assetfileloader_t loader; + uint8_t params[ASSET_BATCH_PARAMS_SIZE]; + size_t dataSize; + assetcachedisposer_t disposer; + void **outPtr; +} assetbatchitem_t; + +struct assetbatch_s { + assetbatchitem_t items[ASSET_BATCH_MAX]; + uint8_t count; + assetbatchcomplete_t onComplete; + assetbatcherror_t onError; + void *context; +}; + +/** + * Initializes a batch, optionally with completion and error callbacks. + * + * @param batch The batch to initialize. + * @param onComplete Called when all items have loaded successfully. May be NULL. + * @param onError Called if any item fails to load. May be NULL. + * @param context Passed through to both callbacks. + */ +void assetBatchInit( + assetbatch_t *batch, + assetbatchcomplete_t onComplete, + assetbatcherror_t onError, + void *context +); + +/** + * Adds an item to the batch. Params are copied into inline storage. + * + * @param batch The batch to add to. + * @param path Asset path, used as the cache key. + * @param loader The loader function for this asset type. + * @param params Loader-specific parameters. Copied — safe to pass stack address. + * @param paramsSize Size of params in bytes. Must be <= ASSET_BATCH_PARAMS_SIZE. + * @param dataSize Size in bytes to allocate for the output struct. + * @param disposer Called on the data before freeing when released. May be NULL. + * @param outPtr Caller's pointer variable. Set to the cache-owned data on load. + */ +void assetBatchAdd( + assetbatch_t *batch, + const char_t *path, + assetfileloader_t loader, + const void *params, + size_t paramsSize, + size_t dataSize, + assetcachedisposer_t disposer, + void **outPtr +); + +/** + * Loads all items in the batch, checking the cache for each. Fires onComplete + * on success or onError on the first failure. Also returns the error for + * synchronous callers. + * + * @param batch The batch to execute. + * @return Error if any item failed to load. + */ +errorret_t assetBatchLoad(assetbatch_t *batch); diff --git a/src/dusk/asset/assetcache.c b/src/dusk/asset/assetcache.c new file mode 100644 index 00000000..be54c497 --- /dev/null +++ b/src/dusk/asset/assetcache.c @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "assetcache.h" +#include "util/memory.h" +#include "util/string.h" +#include "assert/assert.h" + +void assetCacheInit(assetcache_t *cache) { + memoryZero(cache, sizeof(assetcache_t)); +} + +void *assetCacheLookup(assetcache_t *cache, const char_t *path) { + for (uint8_t i = 0; i < cache->count; i++) { + if (stringCompare(cache->entries[i].path, path) == 0) { + return cache->entries[i].data; + } + } + return NULL; +} + +void assetCacheInsert( + assetcache_t *cache, + const char_t *path, + void *data, + assetcachedisposer_t disposer +) { + assertTrue(cache->count < ASSET_CACHE_MAX, "Asset cache is full."); + + assetcacheentry_t *entry = &cache->entries[cache->count++]; + stringCopy(entry->path, path, ASSET_FILE_NAME_MAX); + entry->data = data; + entry->refcount = 1; + entry->disposer = disposer; +} + +void assetCacheRetain(assetcache_t *cache, const char_t *path) { + for (uint8_t i = 0; i < cache->count; i++) { + if (stringCompare(cache->entries[i].path, path) == 0) { + cache->entries[i].refcount++; + return; + } + } + assertTrue(false, "Asset not found in cache for retain."); +} + +static void assetCacheEntryDispose(assetcacheentry_t *entry) { + if (entry->disposer != NULL) { + entry->disposer(entry->data); + } + memoryFree(entry->data); +} + +void assetCacheRelease(assetcache_t *cache, const char_t *path) { + for (uint8_t i = 0; i < cache->count; i++) { + if (stringCompare(cache->entries[i].path, path) == 0) { + assetcacheentry_t *entry = &cache->entries[i]; + entry->refcount--; + if (entry->refcount == 0) { + assetCacheEntryDispose(entry); + for (uint8_t j = i; j < cache->count - 1; j++) { + cache->entries[j] = cache->entries[j + 1]; + } + cache->count--; + } + return; + } + } + assertTrue(false, "Asset not found in cache for release."); +} + +void assetCacheDispose(assetcache_t *cache) { + for (uint8_t i = 0; i < cache->count; i++) { + assetCacheEntryDispose(&cache->entries[i]); + } + memoryZero(cache, sizeof(assetcache_t)); +} diff --git a/src/dusk/asset/assetcache.h b/src/dusk/asset/assetcache.h new file mode 100644 index 00000000..4ab13657 --- /dev/null +++ b/src/dusk/asset/assetcache.h @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "assetfile.h" + +#ifndef ASSET_CACHE_MAX + #define ASSET_CACHE_MAX 64 +#endif + +typedef void (*assetcachedisposer_t)(void *data); + +typedef struct { + char_t path[ASSET_FILE_NAME_MAX]; + void *data; + uint32_t refcount; + assetcachedisposer_t disposer; +} assetcacheentry_t; + +typedef struct { + assetcacheentry_t entries[ASSET_CACHE_MAX]; + uint8_t count; +} assetcache_t; + +/** + * Initializes the asset cache. + * + * @param cache The cache to initialize. + */ +void assetCacheInit(assetcache_t *cache); + +/** + * Looks up a cached asset by path. + * + * @param cache The cache to search. + * @param path The asset path to look up. + * @return Pointer to the cached data, or NULL if not found. + */ +void *assetCacheLookup(assetcache_t *cache, const char_t *path); + +/** + * Inserts a newly loaded asset into the cache with refcount 1. + * + * @param cache The cache to insert into. + * @param path The asset path key. + * @param data Heap-allocated asset data. The cache takes ownership. + * @param disposer Called with data before freeing when refcount reaches 0. + */ +void assetCacheInsert( + assetcache_t *cache, + const char_t *path, + void *data, + assetcachedisposer_t disposer +); + +/** + * Increments the refcount for a cached asset. + * + * @param cache The cache containing the asset. + * @param path The asset path. + */ +void assetCacheRetain(assetcache_t *cache, const char_t *path); + +/** + * Decrements the refcount. Disposes and frees the asset when it reaches 0. + * + * @param cache The cache containing the asset. + * @param path The asset path. + */ +void assetCacheRelease(assetcache_t *cache, const char_t *path); + +/** + * Disposes all remaining cache entries and resets the cache. + * + * @param cache The cache to dispose. + */ +void assetCacheDispose(assetcache_t *cache); diff --git a/src/dusk/asset/loader/display/assettextureloader.c b/src/dusk/asset/loader/display/assettextureloader.c index ca35f4ce..17275962 100644 --- a/src/dusk/asset/loader/display/assettextureloader.c +++ b/src/dusk/asset/loader/display/assettextureloader.c @@ -6,6 +6,7 @@ */ #include "assettextureloader.h" +#include "asset/assetbatch.h" #include "assert/assert.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" @@ -112,4 +113,26 @@ errorret_t assetTextureLoad( .format = format }; return assetLoad(path, assetTextureLoader, ¶ms, out); +} + +static void assetTextureCacheDispose(void *data) { + errorCatch(textureDispose((texture_t*)data)); +} + +void assetBatchTexture( + assetbatch_t *batch, + const char_t *path, + textureformat_t format, + texture_t **out +) { + assettextureloaderparams_t params = { .format = format }; + assetBatchAdd( + batch, + path, + assetTextureLoader, + ¶ms, sizeof(params), + sizeof(texture_t), + assetTextureCacheDispose, + (void**)out + ); } \ No newline at end of file diff --git a/src/dusk/asset/loader/display/assettextureloader.h b/src/dusk/asset/loader/display/assettextureloader.h index e98b5df5..df1d78a8 100644 --- a/src/dusk/asset/loader/display/assettextureloader.h +++ b/src/dusk/asset/loader/display/assettextureloader.h @@ -7,6 +7,7 @@ #pragma once #include "asset/asset.h" +#include "asset/assetbatch.h" #include "display/texture/texture.h" typedef struct { @@ -47,4 +48,20 @@ errorret_t assetTextureLoad( const char_t *path, texture_t *out, const textureformat_t format +); + +/** + * Adds a texture load request to a batch. The cache-owned texture_t* is + * written to *out when the batch completes. + * + * @param batch The batch to add to. + * @param path Path to the texture asset. + * @param format Format of the texture to load. + * @param out Receives a pointer to the loaded texture on completion. + */ +void assetBatchTexture( + assetbatch_t *batch, + const char_t *path, + textureformat_t format, + texture_t **out ); \ No newline at end of file diff --git a/src/dusk/asset/loader/display/assettilesetloader.c b/src/dusk/asset/loader/display/assettilesetloader.c index 25aca58e..88066b34 100644 --- a/src/dusk/asset/loader/display/assettilesetloader.c +++ b/src/dusk/asset/loader/display/assettilesetloader.c @@ -6,6 +6,7 @@ */ #include "assettilesetloader.h" +#include "asset/assetbatch.h" #include "assert/assert.h" #include "util/memory.h" #include "util/endian.h" @@ -77,4 +78,20 @@ errorret_t assetTilesetLoad( tileset_t *out ) { return assetLoad(path, assetTilesetLoader, NULL, out); +} + +void assetBatchTileset( + assetbatch_t *batch, + const char_t *path, + tileset_t **out +) { + assetBatchAdd( + batch, + path, + assetTilesetLoader, + NULL, 0, + sizeof(tileset_t), + NULL, + (void**)out + ); } \ No newline at end of file diff --git a/src/dusk/asset/loader/display/assettilesetloader.h b/src/dusk/asset/loader/display/assettilesetloader.h index cee52296..938dae33 100644 --- a/src/dusk/asset/loader/display/assettilesetloader.h +++ b/src/dusk/asset/loader/display/assettilesetloader.h @@ -7,6 +7,7 @@ #pragma once #include "asset/asset.h" +#include "asset/assetbatch.h" #include "display/texture/tileset.h" /** @@ -27,4 +28,18 @@ errorret_t assetTilesetLoader(assetfile_t *file); errorret_t assetTilesetLoad( const char_t *path, tileset_t *out +); + +/** + * Adds a tileset load request to a batch. The cache-owned tileset_t* is + * written to *out when the batch completes. + * + * @param batch The batch to add to. + * @param path Path to the tileset asset. + * @param out Receives a pointer to the loaded tileset on completion. + */ +void assetBatchTileset( + assetbatch_t *batch, + const char_t *path, + tileset_t **out ); \ No newline at end of file diff --git a/src/dusk/console/console.c b/src/dusk/console/console.c index 9830133a..3e37e8e5 100644 --- a/src/dusk/console/console.c +++ b/src/dusk/console/console.c @@ -67,7 +67,7 @@ errorret_t consoleDraw(void) { for(uint32_t i = 0; i < CONSOLE_HISTORY_MAX; i++) { errorChain(textDraw( - 0, FONT_DEFAULT.tileset.tileHeight * i, + 0, FONT_DEFAULT.tileset->tileHeight * i, CONSOLE.line[i], COLOR_WHITE, &FONT_DEFAULT diff --git a/src/dusk/display/text/font.h b/src/dusk/display/text/font.h index 9e15f034..9cdc7b3b 100644 --- a/src/dusk/display/text/font.h +++ b/src/dusk/display/text/font.h @@ -10,6 +10,6 @@ #include "display/texture/tileset.h" typedef struct { - texture_t texture; - tileset_t tileset; + texture_t *texture; + tileset_t *tileset; } font_t; diff --git a/src/dusk/display/text/text.c b/src/dusk/display/text/text.c index b0f333b7..7177cd13 100644 --- a/src/dusk/display/text/text.c +++ b/src/dusk/display/text/text.c @@ -9,6 +9,8 @@ #include "assert/assert.h" #include "util/memory.h" #include "display/spritebatch/spritebatch.h" +#include "asset/asset.h" +#include "asset/assetbatch.h" #include "asset/loader/display/assettextureloader.h" #include "asset/loader/display/assettilesetloader.h" #include "display/shader/shaderunlit.h" @@ -16,15 +18,19 @@ font_t FONT_DEFAULT; errorret_t textInit(void) { - errorChain(assetTextureLoad( - "ui/minogram.png", &FONT_DEFAULT.texture, TEXTURE_FORMAT_RGBA - )); - errorChain(assetTilesetLoad("ui/minogram.dtf", &FONT_DEFAULT.tileset)); + assetbatch_t batch; + assetBatchInit(&batch, NULL, NULL, NULL); + assetBatchTexture(&batch, "ui/minogram.png", TEXTURE_FORMAT_RGBA, &FONT_DEFAULT.texture); + assetBatchTileset(&batch, "ui/minogram.dtf", &FONT_DEFAULT.tileset); + errorChain(assetBatchLoad(&batch)); errorOk(); } errorret_t textDispose(void) { - errorChain(textureDispose(&FONT_DEFAULT.texture)); + assetCacheRelease(&ASSET.cache, "ui/minogram.png"); + assetCacheRelease(&ASSET.cache, "ui/minogram.dtf"); + FONT_DEFAULT.texture = NULL; + FONT_DEFAULT.tileset = NULL; errorOk(); } @@ -38,21 +44,21 @@ errorret_t textDrawChar( font_t *font ) { int32_t tileIndex = (int32_t)(c) - TEXT_CHAR_START; - if(tileIndex < 0 || tileIndex >= font->tileset.tileCount) { + if(tileIndex < 0 || tileIndex >= font->tileset->tileCount) { tileIndex = ((int32_t)'@') - TEXT_CHAR_START; } assertTrue( - tileIndex >= 0 && tileIndex <= font->tileset.tileCount, + tileIndex >= 0 && tileIndex <= font->tileset->tileCount, "Character is out of bounds for font tiles" ); vec4 uv; - tilesetTileGetUV(&font->tileset, tileIndex, uv); + tilesetTileGetUV(font->tileset, tileIndex, uv); errorChain(spriteBatchPush( x, y, - x + font->tileset.tileWidth, - y + font->tileset.tileHeight, + x + font->tileset->tileWidth, + y + font->tileset->tileHeight, #if MESH_ENABLE_COLOR color, #endif @@ -73,7 +79,7 @@ errorret_t textDraw( float_t posX = x; float_t posY = y; - errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &font->texture)); + errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, font->texture)); #if MESH_ENABLE_COLOR #else @@ -85,12 +91,12 @@ errorret_t textDraw( while((c = text[i++]) != '\0') { if(c == '\n') { posX = x; - posY += font->tileset.tileHeight; + posY += font->tileset->tileHeight; continue; } if(c == ' ') { - posX += font->tileset.tileWidth; + posX += font->tileset->tileWidth; continue; } @@ -101,7 +107,7 @@ errorret_t textDraw( #endif font )); - posX += font->tileset.tileWidth; + posX += font->tileset->tileWidth; } errorOk(); } @@ -117,7 +123,7 @@ void textMeasure( assertNotNull(outHeight, "Output height pointer cannot be NULL"); int32_t width = 0; - int32_t height = font->tileset.tileHeight; + int32_t height = font->tileset->tileHeight; int32_t lineWidth = 0; char_t c; @@ -126,11 +132,11 @@ void textMeasure( if(c == '\n') { if(lineWidth > width) width = lineWidth; lineWidth = 0; - height += font->tileset.tileHeight; + height += font->tileset->tileHeight; continue; } - lineWidth += font->tileset.tileWidth; + lineWidth += font->tileset->tileWidth; } if(lineWidth > width) width = lineWidth; diff --git a/src/dusk/engine/engine.c b/src/dusk/engine/engine.c index 7983e2d1..9158341d 100644 --- a/src/dusk/engine/engine.c +++ b/src/dusk/engine/engine.c @@ -26,8 +26,6 @@ #include "item/backpack.h" #include "save/save.h" -#include "scene/initial/initialscene.h" - engine_t ENGINE; errorret_t engineInit(const int32_t argc, const char_t **argv) { @@ -57,7 +55,7 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) { /* Run the init script. */ consolePrint("Engine initialized"); - sceneSet(SCENE_TYPE_INITIAL); + sceneSet(SCENE_TYPE_OVERWORLD); errorOk(); } diff --git a/src/dusk/entity/component/display/entitycamera.c b/src/dusk/entity/component/display/entitycamera.c index 332e1af9..d9a2727e 100644 --- a/src/dusk/entity/component/display/entitycamera.c +++ b/src/dusk/entity/component/display/entitycamera.c @@ -16,7 +16,7 @@ void entityCameraInit(const entityid_t ent, const componentid_t comp) { ent, comp, COMPONENT_TYPE_CAMERA ); cam->nearClip = 0.1f; - cam->farClip = 100.0f; + cam->farClip = 5000.0f; cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE; cam->perspective.fov = glm_rad(45.0f); } @@ -83,6 +83,30 @@ void entityCameraGetForward(const entityid_t entityId, vec2 out) { out[1] = fz; } +void entityCameraLookAtPixelPerfect( + const entityid_t ent, + const componentid_t posComp, + const componentid_t camComp, + const vec3 point, + const vec3 eyeOffset, + const float_t scale +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + ent, camComp, COMPONENT_TYPE_CAMERA + ); + float_t dist = ( + (float_t)SCREEN.height / (2.0f * scale * tanf(cam->perspective.fov * 0.5f)) + ); + + vec3 eye = { + point[0] + eyeOffset[0], + point[1] + dist + eyeOffset[1], + point[2] + eyeOffset[2] + }; + vec3 up = { 0.0f, 0.0f, -1.0f }; + entityPositionLookAt(ent, posComp, eye, (float_t *)point, up); +} + void entityCameraGetRight(const entityid_t entityId, vec2 out) { componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION); entityposition_t *pos = entityPositionGet(entityId, posComp); diff --git a/src/dusk/entity/component/display/entitycamera.h b/src/dusk/entity/component/display/entitycamera.h index d11be6ae..b866e354 100644 --- a/src/dusk/entity/component/display/entitycamera.h +++ b/src/dusk/entity/component/display/entitycamera.h @@ -76,4 +76,24 @@ void entityCameraGetForward(const entityid_t entityId, vec2 out); * @param entityId The camera entity ID. * @param out Output vec2: {rightX, rightZ} normalized. */ -void entityCameraGetRight(const entityid_t entityId, vec2 out); \ No newline at end of file +void entityCameraGetRight(const entityid_t entityId, vec2 out); + +/** + * Positions the camera to look at a 3D point at a pixel-perfect distance + * derived from the camera's FOV and screen height. + * + * @param ent The camera entity ID. + * @param posComp The position component ID. + * @param camComp The camera component ID. + * @param point World position to look at. + * @param eyeOffset Offset added to the eye position only (not the target). + * @param scale Pixels per world unit. 1.0 = pixel perfect, 2.0 = 2px per unit. + */ +void entityCameraLookAtPixelPerfect( + const entityid_t ent, + const componentid_t posComp, + const componentid_t camComp, + const vec3 point, + const vec3 eyeOffset, + const float_t scale +); \ No newline at end of file diff --git a/src/dusk/entity/component/display/entityrenderable.h b/src/dusk/entity/component/display/entityrenderable.h index f7442517..1e4f578b 100644 --- a/src/dusk/entity/component/display/entityrenderable.h +++ b/src/dusk/entity/component/display/entityrenderable.h @@ -61,4 +61,4 @@ void entityRenderableSetDraw( errorret_t entityRenderableDraw( const entityid_t entityId, const componentid_t componentId -); \ No newline at end of file +); diff --git a/src/dusk/scene/CMakeLists.txt b/src/dusk/scene/CMakeLists.txt index dc56a6b7..cf90daa8 100644 --- a/src/dusk/scene/CMakeLists.txt +++ b/src/dusk/scene/CMakeLists.txt @@ -10,4 +10,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} ) # Subdirectories -add_subdirectory(initial) \ No newline at end of file +add_subdirectory(initial) +add_subdirectory(test) +add_subdirectory(overworld) \ No newline at end of file diff --git a/src/dusk/scene/initial/initialscene.c b/src/dusk/scene/initial/initialscene.c index 1f2ab71c..c4372a7a 100644 --- a/src/dusk/scene/initial/initialscene.c +++ b/src/dusk/scene/initial/initialscene.c @@ -7,118 +7,14 @@ #include "initialscene.h" #include "console/console.h" -#include "display/spritebatch/spritebatch.h" -#include "display/screen/screen.h" -#include "display/text/text.h" -#include "entity/entitymanager.h" -#include "input/input.h" - -#define INITIAL_SCENE_TEST_ENTITY_MAX (ENTITY_COUNT_MAX - 1) - -static entityid_t cameraEntityId; -static componentid_t cameraCompId; -static entityid_t testEntities[INITIAL_SCENE_TEST_ENTITY_MAX]; -static uint8_t testEntityCount = 0; - -static void initialSceneSpawnTestEntity(void) { - if(testEntityCount >= INITIAL_SCENE_TEST_ENTITY_MAX) return; - - entityid_t entity = entityManagerAdd(); - componentid_t posComp = entityAddComponent(entity, COMPONENT_TYPE_POSITION); - (void)entityAddComponent(entity, COMPONENT_TYPE_RENDERABLE); - - const int32_t cols = 20; - const float_t spacing = 1.5f; - int32_t col = testEntityCount % cols; - int32_t row = testEntityCount / cols; - vec3 pos = { - ((float_t)col - (cols - 1) * 0.5f) * spacing, - 0.0f, - (float_t)row * spacing - }; - entityPositionSetLocalPosition(entity, posComp, pos); - - testEntities[testEntityCount++] = entity; -} - -static void initialSceneUpdateCamera(void) { - if(testEntityCount == 0) return; - - float_t minX = FLT_MAX, maxX = -FLT_MAX; - float_t minZ = FLT_MAX, maxZ = -FLT_MAX; - - for(entityid_t i = 0; i < testEntityCount; i++) { - componentid_t posComp = entityGetComponent( - testEntities[i], COMPONENT_TYPE_POSITION - ); - if(posComp == COMPONENT_ID_INVALID) continue; - vec3 pos; - entityPositionGetLocalPosition(testEntities[i], posComp, pos); - if(pos[0] < minX) minX = pos[0]; - if(pos[0] > maxX) maxX = pos[0]; - if(pos[2] < minZ) minZ = pos[2]; - if(pos[2] > maxZ) maxZ = pos[2]; - } - - float_t centerX = (minX + maxX) * 0.5f; - float_t centerZ = (minZ + maxZ) * 0.5f; - float_t extentX = (maxX - minX) * 0.5f + 0.5f; - float_t extentZ = (maxZ - minZ) * 0.5f + 0.5f; - float_t extent = extentX > extentZ ? extentX : extentZ; - float_t dist = extent * 1.5f + 2.0f; - - vec3 target = { centerX, 0.0f, centerZ }; - vec3 eye = { centerX + dist, dist, centerZ + dist }; - vec3 up = { 0.0f, 1.0f, 0.0f }; - entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up); -} void initialSceneInit(void) { consolePrint("Initial scene initialized"); - testEntityCount = 0; - - cameraEntityId = entityManagerAdd(); - cameraCompId = entityAddComponent(cameraEntityId, COMPONENT_TYPE_POSITION); - (void)entityAddComponent(cameraEntityId, COMPONENT_TYPE_CAMERA); - - vec3 eye, target, up; - glm_vec3_zero(target); - glm_vec3_copy((vec3){ 3.0f, 3.0f, 3.0f }, eye); - glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, up); - entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up); - - for(int i = 0; i < 5; i++) initialSceneSpawnTestEntity(); } errorret_t initialSceneUpdate(void) { - if(inputPressed(INPUT_ACTION_ACCEPT)) { - for(int i = 0; i < 5; i++) initialSceneSpawnTestEntity(); - } - - if(inputPressed(INPUT_ACTION_CANCEL)) { - for(int i = 0; i < 5 && testEntityCount > 0; i++) { - entityDispose(testEntities[--testEntityCount]); - } - } - - initialSceneUpdateCamera(); - errorOk(); } -errorret_t initialSceneEntityCountDraw(void) { - char_t buf[32]; - snprintf(buf, sizeof(buf), "Entities: %u", (uint32_t)testEntityCount); - errorChain(textDraw( - 0, (float_t)(SCREEN.height - FONT_DEFAULT.tileset.tileHeight), - buf, COLOR_WHITE, - &FONT_DEFAULT - )); - return spriteBatchFlush(); -} - void initialSceneDispose(void) { - testEntityCount = 0; - cameraEntityId = ENTITY_ID_INVALID; - cameraCompId = COMPONENT_ID_INVALID; } diff --git a/src/dusk/scene/initial/initialscene.h b/src/dusk/scene/initial/initialscene.h index 5af45e4b..03b638b6 100644 --- a/src/dusk/scene/initial/initialscene.h +++ b/src/dusk/scene/initial/initialscene.h @@ -1,6 +1,6 @@ /** * Copyright (c) 2026 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -12,26 +12,6 @@ typedef struct { void *nothing; } initialscene_t; -/** - * Initial scene initializer. - */ void initialSceneInit(void); - -/** - * Initial scene updater. - * - * @return Any error state that happened. - */ errorret_t initialSceneUpdate(void); - -/** - * Initial scene disposer. - */ void initialSceneDispose(void); - -/** - * Draws the entity count UI label for the initial scene. - * - * @return Any error state that happened. - */ -errorret_t initialSceneEntityCountDraw(void); \ No newline at end of file diff --git a/src/dusk/scene/overworld/CMakeLists.txt b/src/dusk/scene/overworld/CMakeLists.txt new file mode 100644 index 00000000..12dda41f --- /dev/null +++ b/src/dusk/scene/overworld/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + overworldground.c + overworldplayer.c + overworldscene.c +) diff --git a/src/dusk/scene/overworld/overworldground.c b/src/dusk/scene/overworld/overworldground.c new file mode 100644 index 00000000..665f14b8 --- /dev/null +++ b/src/dusk/scene/overworld/overworldground.c @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "overworldground.h" +#include "entity/entitymanager.h" +#include "entity/component/display/entityrenderable.h" +#include "entity/component/physics/entityphysics.h" +#include "display/mesh/plane.h" +#include "display/shader/shaderunlit.h" + +#define OVERWORLD_GROUND_SIZE 20.0f + +static errorret_t overworldGroundDraw(void) { + errorChain(shaderSetColor(&SHADER_UNLIT, SHADER_UNLIT_COLOR, COLOR_MAGENTA)); + errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL)); + return meshDraw(&PLANE_MESH_SIMPLE, 0, -1); +} + +void overworldGroundAdd(overworldground_t *ground) { + ground->entityId = entityManagerAdd(); + ground->posCompId = entityAddComponent( + ground->entityId, COMPONENT_TYPE_POSITION + ); + componentid_t renderComp = entityAddComponent( + ground->entityId, COMPONENT_TYPE_RENDERABLE + ); + + vec3 pos = { -OVERWORLD_GROUND_SIZE, 0.0f, -OVERWORLD_GROUND_SIZE }; + vec3 scale = { OVERWORLD_GROUND_SIZE * 2.0f, 1.0f, OVERWORLD_GROUND_SIZE * 2.0f }; + entityPositionSetLocalPosition(ground->entityId, ground->posCompId, pos); + entityPositionSetLocalScale(ground->entityId, ground->posCompId, scale); + entityRenderableSetDraw(ground->entityId, renderComp, overworldGroundDraw); + + ground->physCompId = entityAddComponent(ground->entityId, COMPONENT_TYPE_PHYSICS); + entityPhysicsSetBodyType(ground->entityId, ground->physCompId, PHYSICS_BODY_STATIC); + physicsshape_t shape = { + .type = PHYSICS_SHAPE_PLANE, + .data.plane = { .normal = { 0.0f, 1.0f, 0.0f }, .distance = 0.0f } + }; + entityPhysicsSetShape(ground->entityId, ground->physCompId, shape); +} diff --git a/src/dusk/scene/overworld/overworldground.h b/src/dusk/scene/overworld/overworldground.h new file mode 100644 index 00000000..51d26e20 --- /dev/null +++ b/src/dusk/scene/overworld/overworldground.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" + +typedef struct { + entityid_t entityId; + componentid_t posCompId; + componentid_t physCompId; +} overworldground_t; + +/** + * Creates the ground entity and adds it to the world. + * + * @param ground The ground state to initialize. + */ +void overworldGroundAdd(overworldground_t *ground); diff --git a/src/dusk/scene/overworld/overworldplayer.c b/src/dusk/scene/overworld/overworldplayer.c new file mode 100644 index 00000000..6787a891 --- /dev/null +++ b/src/dusk/scene/overworld/overworldplayer.c @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "overworldplayer.h" +#include "entity/entitymanager.h" +#include "entity/component/physics/entityphysics.h" +#include "input/input.h" + +#define OVERWORLD_PLAYER_SPEED 4.0f +#define OVERWORLD_PLAYER_RUN_SPEED 8.0f + +void overworldPlayerAdd(overworldplayer_t *player) { + player->entityId = entityManagerAdd(); + player->posCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_POSITION); + (void)entityAddComponent(player->entityId, COMPONENT_TYPE_RENDERABLE); + player->physCompId = entityAddComponent(player->entityId, COMPONENT_TYPE_PHYSICS); + + vec3 pos = { 0.0f, 0.5f, 0.0f }; + entityPositionSetLocalPosition(player->entityId, player->posCompId, pos); + + entityPhysicsSetBodyType(player->entityId, player->physCompId, PHYSICS_BODY_DYNAMIC); + physicsshape_t shape = { + .type = PHYSICS_SHAPE_CAPSULE, + .data.capsule = { .radius = 0.4f, .halfHeight = 0.1f } + }; + entityPhysicsSetShape(player->entityId, player->physCompId, shape); +} + +void overworldPlayerUpdate(overworldplayer_t *player) { + vec2 dir; + inputAngle2D( + INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT, + INPUT_ACTION_UP, INPUT_ACTION_DOWN, + dir + ); + + float_t speed = inputIsDown(INPUT_ACTION_CANCEL) + ? OVERWORLD_PLAYER_RUN_SPEED + : OVERWORLD_PLAYER_SPEED; + + vec3 vel; + entityPhysicsGetVelocity(player->entityId, player->physCompId, vel); + vel[0] = dir[0] * speed; + vel[2] = dir[1] * speed; + entityPhysicsSetVelocity(player->entityId, player->physCompId, vel); +} diff --git a/src/dusk/scene/overworld/overworldplayer.h b/src/dusk/scene/overworld/overworldplayer.h new file mode 100644 index 00000000..b8ae2c1e --- /dev/null +++ b/src/dusk/scene/overworld/overworldplayer.h @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "entity/entitybase.h" + +typedef struct { + entityid_t entityId; + componentid_t posCompId; + componentid_t physCompId; +} overworldplayer_t; + +/** + * Creates the player entity and adds it to the world. + * + * @param player The player state to initialize. + */ +void overworldPlayerAdd(overworldplayer_t *player); + +/** + * Updates the player entity, reading input and moving on the XZ plane. + * + * @param player The player state to update. + */ +void overworldPlayerUpdate(overworldplayer_t *player); diff --git a/src/dusk/scene/overworld/overworldscene.c b/src/dusk/scene/overworld/overworldscene.c new file mode 100644 index 00000000..afc21e98 --- /dev/null +++ b/src/dusk/scene/overworld/overworldscene.c @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "overworldscene.h" +#include "overworldplayer.h" +#include "overworldground.h" +#include "console/console.h" +#include "entity/entitymanager.h" +#include "entity/component/physics/entityphysics.h" +#include "scene/scene.h" + +#define OVERWORLD (SCENE.data.overworld) + +void overworldSceneInit(void) { + consolePrint("Overworld scene initialized"); + + OVERWORLD.cameraEntityId = entityManagerAdd(); + OVERWORLD.cameraPosCompId = entityAddComponent( + OVERWORLD.cameraEntityId, COMPONENT_TYPE_POSITION + ); + OVERWORLD.cameraCamCompId = entityAddComponent( + OVERWORLD.cameraEntityId, COMPONENT_TYPE_CAMERA + ); + + overworldGroundAdd(&OVERWORLD.ground); + overworldPlayerAdd(&OVERWORLD.player); + + OVERWORLD.refCubeEntityId = entityManagerAdd(); + componentid_t refPosComp = entityAddComponent( + OVERWORLD.refCubeEntityId, COMPONENT_TYPE_POSITION + ); + (void)entityAddComponent(OVERWORLD.refCubeEntityId, COMPONENT_TYPE_RENDERABLE); + vec3 refPos = { 3.0f, 0.5f, 3.0f }; + entityPositionSetLocalPosition(OVERWORLD.refCubeEntityId, refPosComp, refPos); + + componentid_t refPhysComp = entityAddComponent( + OVERWORLD.refCubeEntityId, COMPONENT_TYPE_PHYSICS + ); + entityPhysicsSetBodyType(OVERWORLD.refCubeEntityId, refPhysComp, PHYSICS_BODY_STATIC); + physicsshape_t refShape = { + .type = PHYSICS_SHAPE_CAPSULE, + .data.capsule = { .radius = 0.4f, .halfHeight = 0.1f } + }; + entityPhysicsSetShape(OVERWORLD.refCubeEntityId, refPhysComp, refShape); +} + +errorret_t overworldSceneUpdate(void) { + overworldPlayerUpdate(&OVERWORLD.player); + + vec3 pos; + entityPositionGetLocalPosition( + OVERWORLD.player.entityId, OVERWORLD.player.posCompId, pos + ); + vec3 center = { pos[0] + 0.5f, pos[1] + 0.5f, pos[2] + 0.5f }; + vec3 eyeOffset = { 0.0f, 0.0f, 5.0f }; + entityCameraLookAtPixelPerfect( + OVERWORLD.cameraEntityId, + OVERWORLD.cameraPosCompId, + OVERWORLD.cameraCamCompId, + center, eyeOffset, 32.0f + ); + + errorOk(); +} + +void overworldSceneDispose(void) { + OVERWORLD.cameraEntityId = ENTITY_ID_INVALID; + OVERWORLD.cameraPosCompId = COMPONENT_ID_INVALID; + OVERWORLD.cameraCamCompId = COMPONENT_ID_INVALID; + OVERWORLD.ground.entityId = ENTITY_ID_INVALID; + OVERWORLD.ground.posCompId = COMPONENT_ID_INVALID; + OVERWORLD.player.entityId = ENTITY_ID_INVALID; + OVERWORLD.player.posCompId = COMPONENT_ID_INVALID; + OVERWORLD.refCubeEntityId = ENTITY_ID_INVALID; +} diff --git a/src/dusk/scene/overworld/overworldscene.h b/src/dusk/scene/overworld/overworldscene.h new file mode 100644 index 00000000..8bcfa436 --- /dev/null +++ b/src/dusk/scene/overworld/overworldscene.h @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" +#include "entity/entitybase.h" +#include "scene/overworld/overworldplayer.h" +#include "scene/overworld/overworldground.h" + +typedef struct { + entityid_t cameraEntityId; + componentid_t cameraPosCompId; + componentid_t cameraCamCompId; + overworldplayer_t player; + overworldground_t ground; + entityid_t refCubeEntityId; +} overworldscene_t; + +void overworldSceneInit(void); +errorret_t overworldSceneUpdate(void); +void overworldSceneDispose(void); diff --git a/src/dusk/scene/scene.h b/src/dusk/scene/scene.h index 30261afc..f4b11000 100644 --- a/src/dusk/scene/scene.h +++ b/src/dusk/scene/scene.h @@ -8,6 +8,8 @@ #pragma once #include "asset/assetfile.h" #include "scene/initial/initialscene.h" +#include "scene/test/testscene.h" +#include "scene/overworld/overworldscene.h" #define SCENE_EVENT_UPDATE_MAX 16 diff --git a/src/dusk/scene/scenelist.h b/src/dusk/scene/scenelist.h index e80ef912..a6521b04 100644 --- a/src/dusk/scene/scenelist.h +++ b/src/dusk/scene/scenelist.h @@ -10,4 +10,6 @@ ((void)) #endif -X(initialscene_t, initial, INITIAL, initialSceneInit, initialSceneUpdate, initialSceneDispose) \ No newline at end of file +X(initialscene_t, initial, INITIAL, initialSceneInit, initialSceneUpdate, initialSceneDispose) +X(testscene_t, test, TEST, testSceneInit, testSceneUpdate, testSceneDispose) +X(overworldscene_t, overworld, OVERWORLD, overworldSceneInit, overworldSceneUpdate, overworldSceneDispose) \ No newline at end of file diff --git a/src/dusk/scene/test/CMakeLists.txt b/src/dusk/scene/test/CMakeLists.txt new file mode 100644 index 00000000..92780e7e --- /dev/null +++ b/src/dusk/scene/test/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Dominic Masters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +target_sources(${DUSK_LIBRARY_TARGET_NAME} + PUBLIC + testscene.c +) diff --git a/src/dusk/scene/test/testscene.c b/src/dusk/scene/test/testscene.c new file mode 100644 index 00000000..3a2012f7 --- /dev/null +++ b/src/dusk/scene/test/testscene.c @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "testscene.h" +#include "console/console.h" +#include "entity/entitymanager.h" +#include "input/input.h" + +#define TEST_SCENE_ENTITY_MAX (ENTITY_COUNT_MAX - 1) + +static entityid_t cameraEntityId; +static componentid_t cameraCompId; +static entityid_t testEntities[TEST_SCENE_ENTITY_MAX]; +static uint8_t testEntityCount = 0; + +static void testSceneSpawnTestEntity(void) { + if(testEntityCount >= TEST_SCENE_ENTITY_MAX) return; + + entityid_t entity = entityManagerAdd(); + componentid_t posComp = entityAddComponent(entity, COMPONENT_TYPE_POSITION); + (void)entityAddComponent(entity, COMPONENT_TYPE_RENDERABLE); + + const int32_t cols = 20; + const float_t spacing = 1.5f; + int32_t col = testEntityCount % cols; + int32_t row = testEntityCount / cols; + vec3 pos = { + ((float_t)col - (cols - 1) * 0.5f) * spacing, + 0.0f, + (float_t)row * spacing + }; + entityPositionSetLocalPosition(entity, posComp, pos); + + testEntities[testEntityCount++] = entity; +} + +static void testSceneUpdateCamera(void) { + if(testEntityCount == 0) return; + + float_t minX = FLT_MAX, maxX = -FLT_MAX; + float_t minZ = FLT_MAX, maxZ = -FLT_MAX; + + for(entityid_t i = 0; i < testEntityCount; i++) { + componentid_t posComp = entityGetComponent( + testEntities[i], COMPONENT_TYPE_POSITION + ); + if(posComp == COMPONENT_ID_INVALID) continue; + vec3 pos; + entityPositionGetLocalPosition(testEntities[i], posComp, pos); + if(pos[0] < minX) minX = pos[0]; + if(pos[0] > maxX) maxX = pos[0]; + if(pos[2] < minZ) minZ = pos[2]; + if(pos[2] > maxZ) maxZ = pos[2]; + } + + float_t centerX = (minX + maxX) * 0.5f; + float_t centerZ = (minZ + maxZ) * 0.5f; + float_t extentX = (maxX - minX) * 0.5f + 0.5f; + float_t extentZ = (maxZ - minZ) * 0.5f + 0.5f; + float_t extent = extentX > extentZ ? extentX : extentZ; + float_t dist = extent * 1.5f + 2.0f; + + vec3 target = { centerX, 0.0f, centerZ }; + vec3 eye = { centerX + dist, dist, centerZ + dist }; + vec3 up = { 0.0f, 1.0f, 0.0f }; + entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up); +} + +void testSceneInit(void) { + consolePrint("Test scene initialized"); + testEntityCount = 0; + + cameraEntityId = entityManagerAdd(); + cameraCompId = entityAddComponent(cameraEntityId, COMPONENT_TYPE_POSITION); + (void)entityAddComponent(cameraEntityId, COMPONENT_TYPE_CAMERA); + + vec3 eye, target, up; + glm_vec3_zero(target); + glm_vec3_copy((vec3){ 3.0f, 3.0f, 3.0f }, eye); + glm_vec3_copy((vec3){ 0.0f, 1.0f, 0.0f }, up); + entityPositionLookAt(cameraEntityId, cameraCompId, eye, target, up); + + for(int i = 0; i < 5; i++) testSceneSpawnTestEntity(); +} + +errorret_t testSceneUpdate(void) { + if(inputPressed(INPUT_ACTION_ACCEPT)) { + for(int i = 0; i < 5; i++) testSceneSpawnTestEntity(); + } + + if(inputPressed(INPUT_ACTION_CANCEL)) { + for(int i = 0; i < 5 && testEntityCount > 0; i++) { + entityDispose(testEntities[--testEntityCount]); + } + } + + testSceneUpdateCamera(); + + errorOk(); +} + +void testSceneDispose(void) { + testEntityCount = 0; + cameraEntityId = ENTITY_ID_INVALID; + cameraCompId = COMPONENT_ID_INVALID; +} diff --git a/src/dusk/scene/test/testscene.h b/src/dusk/scene/test/testscene.h new file mode 100644 index 00000000..7ef8f449 --- /dev/null +++ b/src/dusk/scene/test/testscene.h @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "error/error.h" + +typedef struct { + void *nothing; +} testscene_t; + +void testSceneInit(void); +errorret_t testSceneUpdate(void); +void testSceneDispose(void); diff --git a/src/dusk/ui/uielement.c b/src/dusk/ui/uielement.c index 7efdfdf7..4efb19c1 100644 --- a/src/dusk/ui/uielement.c +++ b/src/dusk/ui/uielement.c @@ -12,7 +12,6 @@ #include "engine/engine.h" #include "ui/uitextbox.h" #include "ui/uifullbox.h" -#include "scene/initial/initialscene.h" uielement_t UI_ELEMENTS[] = { // Fullbox under: above scene, below system UI. @@ -23,8 +22,6 @@ uielement_t UI_ELEMENTS[] = { { .type = UI_ELEMENT_TYPE_NATIVE, .draw = consoleDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFPSDraw }, { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiTextboxDraw }, - { .type = UI_ELEMENT_TYPE_NATIVE, .draw = initialSceneEntityCountDraw }, - // Fullbox over: above absolutely everything. { .type = UI_ELEMENT_TYPE_NATIVE, .draw = uiFullboxOverDraw }, diff --git a/src/dusk/ui/uitextbox.c b/src/dusk/ui/uitextbox.c index 94f29b92..7652c417 100644 --- a/src/dusk/ui/uitextbox.c +++ b/src/dusk/ui/uitextbox.c @@ -24,8 +24,8 @@ errorret_t uiTextboxInit(void) { UI_TEXTBOX.font = &FONT_DEFAULT; - float_t fontW = (float_t)FONT_DEFAULT.tileset.tileWidth; - float_t fontH = (float_t)FONT_DEFAULT.tileset.tileHeight; + float_t fontW = (float_t)FONT_DEFAULT.tileset->tileWidth; + float_t fontH = (float_t)FONT_DEFAULT.tileset->tileHeight; float_t tbHeight = ( (float_t)UI_TEXTBOX_LINES_PER_PAGE_MAX * fontH + (float_t)(UI_TEXTBOX_LINES_PER_PAGE_MAX - 1) * UI_TEXTBOX_LINE_SPACING + @@ -39,8 +39,8 @@ errorret_t uiTextboxInit(void) { UI_TEXTBOX.frame.tileset.columns = 3; UI_TEXTBOX.frame.tileset.rows = 3; UI_TEXTBOX.frame.tileset.tileCount = 9; - UI_TEXTBOX.frame.tileset.tileWidth = FONT_DEFAULT.tileset.tileWidth; - UI_TEXTBOX.frame.tileset.tileHeight = FONT_DEFAULT.tileset.tileHeight; + UI_TEXTBOX.frame.tileset.tileWidth = FONT_DEFAULT.tileset->tileWidth; + UI_TEXTBOX.frame.tileset.tileHeight = FONT_DEFAULT.tileset->tileHeight; UI_TEXTBOX.frame.tileset.uv[0] = 1.0f / 3.0f; UI_TEXTBOX.frame.tileset.uv[1] = 1.0f / 3.0f; UI_TEXTBOX.frame.texture = &TEXTURE_WHITE; @@ -57,8 +57,8 @@ void uiTextboxBuildLayout(void) { float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; - float_t fontW = (float_t)UI_TEXTBOX.font->tileset.tileWidth; - float_t fontH = (float_t)UI_TEXTBOX.font->tileset.tileHeight; + float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth; + float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight; if(fontW <= 0.0f || fontH <= 0.0f) return; @@ -217,7 +217,7 @@ errorret_t uiTextboxDraw(void) { errorChain(spriteBatchFlush()); errorChain(shaderSetTexture( - &SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &UI_TEXTBOX.font->texture + &SHADER_UNLIT, SHADER_UNLIT_TEXTURE, UI_TEXTBOX.font->texture )); #if MESH_ENABLE_COLOR #else @@ -228,8 +228,8 @@ errorret_t uiTextboxDraw(void) { float_t frameTileW = (float_t)UI_TEXTBOX.frame.tileset.tileWidth; float_t frameTileH = (float_t)UI_TEXTBOX.frame.tileset.tileHeight; - float_t fontW = (float_t)UI_TEXTBOX.font->tileset.tileWidth; - float_t fontH = (float_t)UI_TEXTBOX.font->tileset.tileHeight; + float_t fontW = (float_t)UI_TEXTBOX.font->tileset->tileWidth; + float_t fontH = (float_t)UI_TEXTBOX.font->tileset->tileHeight; float_t contentX = UI_TEXTBOX.x + frameTileW; float_t contentY = UI_TEXTBOX.y + frameTileH;