Asset refactor

This commit is contained in:
2026-05-21 23:42:56 -05:00
parent 653ca9a72d
commit f68b31158f
35 changed files with 903 additions and 167 deletions
+2
View File
@@ -8,6 +8,8 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
asset.c
assetfile.c
assetcache.c
assetbatch.c
)
# Subdirs
+5 -2
View File
@@ -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();
}
+2
View File
@@ -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;
+81
View File
@@ -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();
}
+90
View File
@@ -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);
+81
View File
@@ -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));
}
+81
View File
@@ -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);
@@ -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, &params, 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,
&params, sizeof(params),
sizeof(texture_t),
assetTextureCacheDispose,
(void**)out
);
}
@@ -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
);
@@ -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
);
}
@@ -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
);
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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;
+23 -17
View File
@@ -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;
+1 -3
View File
@@ -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();
}
@@ -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);
@@ -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);
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
);
@@ -61,4 +61,4 @@ void entityRenderableSetDraw(
errorret_t entityRenderableDraw(
const entityid_t entityId,
const componentid_t componentId
);
);
+3 -1
View File
@@ -10,4 +10,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
)
# Subdirectories
add_subdirectory(initial)
add_subdirectory(initial)
add_subdirectory(test)
add_subdirectory(overworld)
-104
View File
@@ -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;
}
+1 -21
View File
@@ -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);
+11
View File
@@ -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
)
@@ -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);
}
@@ -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);
@@ -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);
}
@@ -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);
+79
View File
@@ -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;
}
+25
View File
@@ -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);
+2
View File
@@ -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
+3 -1
View File
@@ -10,4 +10,6 @@
((void))
#endif
X(initialscene_t, initial, INITIAL, initialSceneInit, initialSceneUpdate, initialSceneDispose)
X(initialscene_t, initial, INITIAL, initialSceneInit, initialSceneUpdate, initialSceneDispose)
X(testscene_t, test, TEST, testSceneInit, testSceneUpdate, testSceneDispose)
X(overworldscene_t, overworld, OVERWORLD, overworldSceneInit, overworldSceneUpdate, overworldSceneDispose)
+9
View File
@@ -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
)
+110
View File
@@ -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;
}
+17
View File
@@ -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);
-3
View File
@@ -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 },
+9 -9
View File
@@ -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;