diff --git a/src/dusk/animation/animation.c b/src/dusk/animation/animation.c index 1380bd6b..459e65a1 100644 --- a/src/dusk/animation/animation.c +++ b/src/dusk/animation/animation.c @@ -6,7 +6,7 @@ #include "animation.h" #include "assert/assert.h" #include "util/memory.h" -#include "util/math.h" +#include "util/fixed.h" void animationInit( animation_t *anim, @@ -21,12 +21,12 @@ void animationInit( anim->keyframeCount = keyframeCount; } -float_t animationGetValue(animation_t *anim, const float_t time) { +fixed_t animationGetValue(animation_t *anim, const fixed_t time) { assertNotNull(anim, "Animation pointer cannot be null."); assertNotNull(anim->keyframes, "Keyframes pointer cannot be null."); assertTrue(anim->keyframeCount > 0, "Keyframe count invalid."); assertTrue(time >= 0, "Time must be non-negative."); - + keyframe_t *start; keyframe_t *end; keyframe_t *last = anim->keyframes + anim->keyframeCount - 1; @@ -47,6 +47,13 @@ float_t animationGetValue(animation_t *anim, const float_t time) { } } while(true); - float_t t = (time - start->time) / (end->time - start->time); - return mathLerp(start->value, end->value, easingApply(start->easing, t)); + fixed_t span = fixedSub(end->time, start->time); + fixed_t progress = span != 0 + ? fixedDiv(fixedSub(time, start->time), span) + : FIXED_ONE; + return fixedLerp( + start->value, + end->value, + easingApply(start->easing, progress) + ); } \ No newline at end of file diff --git a/src/dusk/animation/animation.h b/src/dusk/animation/animation.h index 5a76bdaf..3f701bc9 100644 --- a/src/dusk/animation/animation.h +++ b/src/dusk/animation/animation.h @@ -31,4 +31,4 @@ void animationInit( * @param time The time at which to get the value, in seconds. * @return The value of the animation at the given time. */ -float_t animationGetValue(animation_t *anim, const float_t time); \ No newline at end of file +fixed_t animationGetValue(animation_t *anim, const fixed_t time); \ No newline at end of file diff --git a/src/dusk/animation/easing.c b/src/dusk/animation/easing.c index 19cbf12a..582d091b 100644 --- a/src/dusk/animation/easing.c +++ b/src/dusk/animation/easing.c @@ -6,6 +6,11 @@ #include "easing.h" #include "assert/assert.h" #include "util/math.h" +#include "util/fixed.h" + +#define EASING_C1 1.70158f +#define EASING_C2 (EASING_C1 * 1.525f) +#define EASING_C3 (EASING_C1 + 1.0f) const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = { easingLinear, @@ -26,86 +31,101 @@ const easingfn_t EASING_FUNCTIONS[EASING_COUNT] = { easingInOutBack, }; -float_t easingApply(const easingtype_t type, const float_t t) { +fixed_t easingApply(const easingtype_t type, const fixed_t t) { assertTrue(type < EASING_COUNT, "Invalid easing type"); return EASING_FUNCTIONS[type](t); } -float_t easingLinear(const float_t t) { +fixed_t easingLinear(const fixed_t t) { return t; } -float_t easingInSine(const float_t t) { - return 1.0f - cosf(t * MATH_PI * 0.5f); +fixed_t easingInSine(const fixed_t t) { + float_t f = fixedToFloat(t); + return fixedFromFloat(1.0f - cosf(f * MATH_PI * 0.5f)); } -float_t easingOutSine(const float_t t) { - return sinf(t * MATH_PI * 0.5f); +fixed_t easingOutSine(const fixed_t t) { + float_t f = fixedToFloat(t); + return fixedFromFloat(sinf(f * MATH_PI * 0.5f)); } -float_t easingInOutSine(const float_t t) { - return -(cosf(MATH_PI * t) - 1.0f) * 0.5f; +fixed_t easingInOutSine(const fixed_t t) { + float_t f = fixedToFloat(t); + return fixedFromFloat(-(cosf(MATH_PI * f) - 1.0f) * 0.5f); } -float_t easingInQuad(const float_t t) { - return t * t; +fixed_t easingInQuad(const fixed_t t) { + float_t f = fixedToFloat(t); + return fixedFromFloat(f * f); } -float_t easingOutQuad(const float_t t) { - float_t u = 1.0f - t; - return 1.0f - u * u; +fixed_t easingOutQuad(const fixed_t t) { + float_t f = fixedToFloat(t); + float_t u = 1.0f - f; + return fixedFromFloat(1.0f - u * u); } -float_t easingInOutQuad(const float_t t) { - if(t < 0.5f) return 2.0f * t * t; - float_t u = -2.0f * t + 2.0f; - return 1.0f - u * u * 0.5f; +fixed_t easingInOutQuad(const fixed_t t) { + float_t f = fixedToFloat(t); + if(f < 0.5f) return fixedFromFloat(2.0f * f * f); + float_t u = -2.0f * f + 2.0f; + return fixedFromFloat(1.0f - u * u * 0.5f); } -float_t easingInCubic(const float_t t) { - return t * t * t; +fixed_t easingInCubic(const fixed_t t) { + float_t f = fixedToFloat(t); + return fixedFromFloat(f * f * f); } -float_t easingOutCubic(const float_t t) { - float_t u = 1.0f - t; - return 1.0f - u * u * u; +fixed_t easingOutCubic(const fixed_t t) { + float_t f = fixedToFloat(t); + float_t u = 1.0f - f; + return fixedFromFloat(1.0f - u * u * u); } -float_t easingInOutCubic(const float_t t) { - if(t < 0.5f) return 4.0f * t * t * t; - float_t u = -2.0f * t + 2.0f; - return 1.0f - u * u * u * 0.5f; +fixed_t easingInOutCubic(const fixed_t t) { + float_t f = fixedToFloat(t); + if(f < 0.5f) return fixedFromFloat(4.0f * f * f * f); + float_t u = -2.0f * f + 2.0f; + return fixedFromFloat(1.0f - u * u * u * 0.5f); } -float_t easingInQuart(const float_t t) { - return t * t * t * t; +fixed_t easingInQuart(const fixed_t t) { + float_t f = fixedToFloat(t); + return fixedFromFloat(f * f * f * f); } -float_t easingOutQuart(const float_t t) { - float_t u = 1.0f - t; - return 1.0f - u * u * u * u; +fixed_t easingOutQuart(const fixed_t t) { + float_t f = fixedToFloat(t); + float_t u = 1.0f - f; + return fixedFromFloat(1.0f - u * u * u * u); } -float_t easingInOutQuart(const float_t t) { - if(t < 0.5f) return 8.0f * t * t * t * t; - float_t u = -2.0f * t + 2.0f; - return 1.0f - u * u * u * u * 0.5f; +fixed_t easingInOutQuart(const fixed_t t) { + float_t f = fixedToFloat(t); + if(f < 0.5f) return fixedFromFloat(8.0f * f * f * f * f); + float_t u = -2.0f * f + 2.0f; + return fixedFromFloat(1.0f - u * u * u * u * 0.5f); } -float_t easingInBack(const float_t t) { - return EASING_C3 * t * t * t - EASING_C1 * t * t; +fixed_t easingInBack(const fixed_t t) { + float_t f = fixedToFloat(t); + return fixedFromFloat(EASING_C3 * f * f * f - EASING_C1 * f * f); } -float_t easingOutBack(const float_t t) { - float_t u = t - 1.0f; - return 1.0f + EASING_C3 * u * u * u + EASING_C1 * u * u; +fixed_t easingOutBack(const fixed_t t) { + float_t f = fixedToFloat(t); + float_t u = f - 1.0f; + return fixedFromFloat(1.0f + EASING_C3 * u * u * u + EASING_C1 * u * u); } -float_t easingInOutBack(const float_t t) { - if(t < 0.5f) { - float_t u = 2.0f * t; - return u * u * ((EASING_C2 + 1.0f) * u - EASING_C2) * 0.5f; +fixed_t easingInOutBack(const fixed_t t) { + float_t f = fixedToFloat(t); + if(f < 0.5f) { + float_t u = 2.0f * f; + return fixedFromFloat(u * u * ((EASING_C2 + 1.0f) * u - EASING_C2) * 0.5f); } - float_t u = 2.0f * t - 2.0f; - return (u * u * ((EASING_C2 + 1.0f) * u + EASING_C2) + 2.0f) * 0.5f; + float_t u = 2.0f * f - 2.0f; + return fixedFromFloat((u * u * ((EASING_C2 + 1.0f) * u + EASING_C2) + 2.0f) * 0.5f); } diff --git a/src/dusk/animation/easing.h b/src/dusk/animation/easing.h index 117988b5..e864dc45 100644 --- a/src/dusk/animation/easing.h +++ b/src/dusk/animation/easing.h @@ -5,11 +5,7 @@ #pragma once #include "dusk.h" - -#define EASING_PI 3.14159265358979323846f -#define EASING_C1 1.70158f -#define EASING_C2 (EASING_C1 * 1.525f) -#define EASING_C3 (EASING_C1 + 1.0f) +#include "util/fixed.h" typedef enum { EASING_LINEAR, @@ -32,32 +28,32 @@ typedef enum { EASING_COUNT } easingtype_t; -typedef float_t (*easingfn_t)(const float_t t); +typedef fixed_t (*easingfn_t)(const fixed_t t); extern const easingfn_t EASING_FUNCTIONS[EASING_COUNT]; /** * Applies the specified easing function to t. - * + * * @param type The easing type to apply. - * @param t The input time, in the range [0, 1]. - * @return The eased value, in the range [0, 1]. + * @param t The input progress in [0, FIXED_ONE]. + * @return The eased value in [0, FIXED_ONE]. */ -float_t easingApply(const easingtype_t type, const float_t t); +fixed_t easingApply(const easingtype_t type, const fixed_t t); -float_t easingLinear(const float_t t); -float_t easingInSine(const float_t t); -float_t easingOutSine(const float_t t); -float_t easingInOutSine(const float_t t); -float_t easingInQuad(const float_t t); -float_t easingOutQuad(const float_t t); -float_t easingInOutQuad(const float_t t); -float_t easingInCubic(const float_t t); -float_t easingOutCubic(const float_t t); -float_t easingInOutCubic(const float_t t); -float_t easingInQuart(const float_t t); -float_t easingOutQuart(const float_t t); -float_t easingInOutQuart(const float_t t); -float_t easingInBack(const float_t t); -float_t easingOutBack(const float_t t); -float_t easingInOutBack(const float_t t); +fixed_t easingLinear(const fixed_t t); +fixed_t easingInSine(const fixed_t t); +fixed_t easingOutSine(const fixed_t t); +fixed_t easingInOutSine(const fixed_t t); +fixed_t easingInQuad(const fixed_t t); +fixed_t easingOutQuad(const fixed_t t); +fixed_t easingInOutQuad(const fixed_t t); +fixed_t easingInCubic(const fixed_t t); +fixed_t easingOutCubic(const fixed_t t); +fixed_t easingInOutCubic(const fixed_t t); +fixed_t easingInQuart(const fixed_t t); +fixed_t easingOutQuart(const fixed_t t); +fixed_t easingInOutQuart(const fixed_t t); +fixed_t easingInBack(const fixed_t t); +fixed_t easingOutBack(const fixed_t t); +fixed_t easingInOutBack(const fixed_t t); diff --git a/src/dusk/animation/keyframe.h b/src/dusk/animation/keyframe.h index 6ea03984..49fd60c9 100644 --- a/src/dusk/animation/keyframe.h +++ b/src/dusk/animation/keyframe.h @@ -5,9 +5,10 @@ #pragma once #include "easing.h" +#include "util/fixed.h" typedef struct { - float_t time; - float_t value; + fixed_t time; + fixed_t value; easingtype_t easing; } keyframe_t; \ No newline at end of file diff --git a/src/dusk/asset/loader/locale/assetlocaleloader.c b/src/dusk/asset/loader/locale/assetlocaleloader.c index 6d7a825c..8a0bf0de 100644 --- a/src/dusk/asset/loader/locale/assetlocaleloader.c +++ b/src/dusk/asset/loader/locale/assetlocaleloader.c @@ -816,17 +816,24 @@ errorret_t assetLocaleGetStringWithArgs( case 'f': if( args[nextArg].type != ASSET_LOCALE_ARG_FLOAT && - args[nextArg].type != ASSET_LOCALE_ARG_INT + args[nextArg].type != ASSET_LOCALE_ARG_INT && + args[nextArg].type != ASSET_LOCALE_ARG_FIXED ) { memoryFree(format); - errorThrow("Expected float or int locale argument for ID: %s", id); + errorThrow( + "Expected float, fixed, or int locale argument for ID: %s", + id + ); } - float_t floatValue = ( - args[nextArg].type == ASSET_LOCALE_ARG_FLOAT ? - args[nextArg].floatValue : - (float_t)args[nextArg].intValue - ); + float_t floatValue; + if(args[nextArg].type == ASSET_LOCALE_ARG_FLOAT) { + floatValue = args[nextArg].floatValue; + } else if(args[nextArg].type == ASSET_LOCALE_ARG_FIXED) { + floatValue = fixedToFloat(args[nextArg].fixedValue); + } else { + floatValue = (float_t)args[nextArg].intValue; + } written = snprintf( valueBuffer, diff --git a/src/dusk/asset/loader/locale/assetlocaleloader.h b/src/dusk/asset/loader/locale/assetlocaleloader.h index 79dfa31e..20f22702 100644 --- a/src/dusk/asset/loader/locale/assetlocaleloader.h +++ b/src/dusk/asset/loader/locale/assetlocaleloader.h @@ -7,6 +7,7 @@ #pragma once #include "asset/assetfile.h" +#include "util/fixed.h" typedef struct assetloading_s assetloading_t; typedef struct assetentry_s assetentry_t; @@ -51,7 +52,8 @@ typedef enum { typedef enum { ASSET_LOCALE_ARG_STRING, ASSET_LOCALE_ARG_INT, - ASSET_LOCALE_ARG_FLOAT + ASSET_LOCALE_ARG_FLOAT, + ASSET_LOCALE_ARG_FIXED } assetlocaleargtype_t; /** @@ -67,6 +69,7 @@ typedef struct { const char_t *stringValue; int32_t intValue; float_t floatValue; + fixed_t fixedValue; }; } assetlocalearg_t; diff --git a/src/dusk/rpg/cutscene/item/cutsceneitem.c b/src/dusk/rpg/cutscene/item/cutsceneitem.c index 18a8b582..c04b6153 100644 --- a/src/dusk/rpg/cutscene/item/cutsceneitem.c +++ b/src/dusk/rpg/cutscene/item/cutsceneitem.c @@ -44,7 +44,7 @@ void cutsceneItemUpdate(const cutsceneitem_t *item, cutsceneitemdata_t *data) { break; case CUTSCENE_ITEM_TYPE_WAIT: - data->wait -= TIME.delta; + data->wait = fixedSub(data->wait, TIME.delta); if(data->wait <= 0) cutsceneSystemNext(); break; diff --git a/src/dusk/rpg/cutscene/item/cutscenewait.h b/src/dusk/rpg/cutscene/item/cutscenewait.h index 9d590df2..7c774fc7 100644 --- a/src/dusk/rpg/cutscene/item/cutscenewait.h +++ b/src/dusk/rpg/cutscene/item/cutscenewait.h @@ -7,6 +7,7 @@ #pragma once #include "dusk.h" +#include "util/fixed.h" -typedef float_t cutscenewait_t; -typedef float_t cutscenewaitdata_t; \ No newline at end of file +typedef fixed_t cutscenewait_t; +typedef fixed_t cutscenewaitdata_t; \ No newline at end of file diff --git a/src/dusk/rpg/cutscene/scene/testcutscene.h b/src/dusk/rpg/cutscene/scene/testcutscene.h index bcdffb21..7c54d154 100755 --- a/src/dusk/rpg/cutscene/scene/testcutscene.h +++ b/src/dusk/rpg/cutscene/scene/testcutscene.h @@ -10,7 +10,7 @@ static const cutsceneitem_t TEST_CUTSCENE_ONE_ITEMS[] = { { .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "This is a test cutscene.", .position = RPG_TEXTBOX_POS_BOTTOM } }, - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 2.0f }, + { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = FIXED(2.0f) }, { .type = CUTSCENE_ITEM_TYPE_TEXT, .text = { .text = "It has multiple lines of text.\nAnd waits in between.", .position = RPG_TEXTBOX_POS_TOP } }, }; @@ -20,7 +20,7 @@ static const cutscene_t TEST_CUTSCENE_ONE = { }; static const cutsceneitem_t TEST_CUTSCENE_TWO_ITEMS[] = { - { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = 1.0f }, + { .type = CUTSCENE_ITEM_TYPE_WAIT, .wait = FIXED(1.0f) }, { .type = CUTSCENE_ITEM_TYPE_CUTSCENE, .cutscene = &TEST_CUTSCENE_ONE }, }; diff --git a/src/dusk/rpg/entity/entity.c b/src/dusk/rpg/entity/entity.c index 98052149..e0fe82aa 100644 --- a/src/dusk/rpg/entity/entity.c +++ b/src/dusk/rpg/entity/entity.c @@ -60,16 +60,13 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { entity->direction = direction; // Where are we moving? - worldpos_t newPos = entity->position; + worldpos_t cur = fixedToWorldPos(entity->position); worldunits_t relX, relY; - { - entityDirGetRelative(direction, &relX, &relY); - newPos.x += relX; - newPos.y += relY; - } + entityDirGetRelative(direction, &relX, &relY); + worldpos_t newPos = { cur.x + relX, cur.y + relY, cur.z }; // Get tile under foot - tile_t tileCurrent = mapGetTile(entity->position); + tile_t tileCurrent = mapGetTile(cur); tile_t tileNew = mapGetTile(newPos); bool_t fall = false; bool_t raise = false; @@ -156,27 +153,24 @@ void entityWalk(entity_t *entity, const entitydir_t direction) { do { if(other == entity) continue; if(other->type == ENTITY_TYPE_NULL) continue; - if(!worldPosIsEqual(other->position, newPos)) continue; + if(!worldPosIsEqual(fixedToWorldPos(other->position), newPos)) continue; return;// Blocked } while(++other, other < &ENTITIES[ENTITY_COUNT]); - entity->lastPosition = entity->position; - entity->position = newPos; - entity->animation = ENTITY_ANIM_WALK; - entity->animTime = ENTITY_ANIM_WALK_DURATION;// TODO: Running vs walking + if(raise) newPos.z += 1; + else if(fall) newPos.z -= 1; - if(raise) { - entity->position.z += 1; - } else if(fall) { - entity->position.z -= 1; - } + memoryCopy(entity->lastPosition, entity->position, sizeof(entity->lastPosition)); + worldPosToFixed(&newPos, entity->position); + entity->animation = ENTITY_ANIM_WALK; + entity->animTime = ENTITY_ANIM_WALK_DURATION; } entity_t * entityGetAt(const worldpos_t position) { entity_t *ent = ENTITIES; do { if(ent->type == ENTITY_TYPE_NULL) continue; - if(!worldPosIsEqual(ent->position, position)) continue; + if(!worldPosIsEqual(fixedToWorldPos(ent->position), position)) continue; return ent; } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); @@ -190,4 +184,12 @@ uint8_t entityGetAvailable() { } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); return 0xFF; +} + +bool_t entityCanWalk(const entity_t *entity) { + return entity->animation == ENTITY_ANIM_IDLE; +} + +bool_t entityCanTurn(const entity_t *entity) { + return entity->animation == ENTITY_ANIM_IDLE; } \ No newline at end of file diff --git a/src/dusk/rpg/entity/entity.h b/src/dusk/rpg/entity/entity.h index e6a93b5c..38ef9884 100644 --- a/src/dusk/rpg/entity/entity.h +++ b/src/dusk/rpg/entity/entity.h @@ -20,12 +20,11 @@ typedef struct entity_s { // Movement entitydir_t direction; - worldpos_t position; - tilepos_t subPosition; - worldpos_t lastPosition; + fixed_t position[3]; + fixed_t lastPosition[3]; entityanim_t animation; - float_t animTime; + fixed_t animTime; } entity_t; extern entity_t ENTITIES[ENTITY_COUNT]; @@ -72,7 +71,23 @@ entity_t *entityGetAt(const worldpos_t pos); /** * Gets an available entity index. - * + * * @return The index of an available entity, or 0xFF if none are available. */ -uint8_t entityGetAvailable(); \ No newline at end of file +uint8_t entityGetAvailable(); + +/** + * Returns true if the entity is in a state where it can begin walking. + * + * @param entity Pointer to the entity to check. + * @return true if the entity can walk, false otherwise. + */ +bool_t entityCanWalk(const entity_t *entity); + +/** + * Returns true if the entity is in a state where it can begin turning. + * + * @param entity Pointer to the entity to check. + * @return true if the entity can turn, false otherwise. + */ +bool_t entityCanTurn(const entity_t *entity); \ No newline at end of file diff --git a/src/dusk/rpg/entity/entityanim.c b/src/dusk/rpg/entity/entityanim.c index d5024815..0d8374df 100644 --- a/src/dusk/rpg/entity/entityanim.c +++ b/src/dusk/rpg/entity/entityanim.c @@ -1,6 +1,6 @@ /** * Copyright (c) 2025 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ @@ -11,9 +11,9 @@ void entityAnimUpdate(entity_t *entity) { if(entity->animation == ENTITY_ANIM_IDLE) return; - entity->animTime -= TIME.delta; + entity->animTime = fixedSub(entity->animTime, TIME.delta); if(entity->animTime <= 0) { entity->animation = ENTITY_ANIM_IDLE; entity->animTime = 0; } -} \ No newline at end of file +} diff --git a/src/dusk/rpg/entity/entityanim.h b/src/dusk/rpg/entity/entityanim.h index 9fb8d9ea..a09f0847 100644 --- a/src/dusk/rpg/entity/entityanim.h +++ b/src/dusk/rpg/entity/entityanim.h @@ -7,9 +7,10 @@ #pragma once #include "dusk.h" +#include "util/fixed.h" -#define ENTITY_ANIM_TURN_DURATION 0.06f -#define ENTITY_ANIM_WALK_DURATION 0.1f +#define ENTITY_ANIM_TURN_DURATION FIXED(0.12f) +#define ENTITY_ANIM_WALK_DURATION FIXED(0.1f) typedef struct entity_s entity_t; diff --git a/src/dusk/rpg/entity/player.c b/src/dusk/rpg/entity/player.c index cf33d562..22b915c5 100644 --- a/src/dusk/rpg/entity/player.c +++ b/src/dusk/rpg/entity/player.c @@ -19,33 +19,33 @@ void playerInput(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); // Turn - const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP; - do { - if(!inputIsDown(dirMap->action)) continue; - if(entity->direction == dirMap->direction) continue; - return entityTurn(entity, dirMap->direction); - } while((++dirMap)->action != 0xFF); + if(entityCanTurn(entity)) { + const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP; + do { + if(!inputIsDown(dirMap->action)) continue; + if(entity->direction == dirMap->direction) continue; + return entityTurn(entity, dirMap->direction); + } while((++dirMap)->action != 0xFF); + } // Walk - dirMap = PLAYER_INPUT_DIR_MAP; - do { - if(!inputIsDown(dirMap->action)) continue; - if(entity->direction != dirMap->direction) continue; - return entityWalk(entity, dirMap->direction); - } while((++dirMap)->action != 0xFF); + if(entityCanWalk(entity)) { + const playerinputdirmap_t *dirMap = PLAYER_INPUT_DIR_MAP; + do { + if(!inputIsDown(dirMap->action)) continue; + if(entity->direction != dirMap->direction) continue; + return entityWalk(entity, dirMap->direction); + } while((++dirMap)->action != 0xFF); + } // Interaction if(inputPressed(INPUT_ACTION_ACCEPT)) { - worldunit_t x, y, z; - { - worldunits_t relX, relY; - entityDirGetRelative(entity->direction, &relX, &relY); - x = entity->position.x + relX; - y = entity->position.y + relY; - z = entity->position.z; - } - - entity_t *interact = entityGetAt((worldpos_t){ x, y, z }); + worldunits_t relX, relY; + entityDirGetRelative(entity->direction, &relX, &relY); + worldpos_t cur = fixedToWorldPos(entity->position); + entity_t *interact = entityGetAt( + (worldpos_t){ cur.x + relX, cur.y + relY, cur.z } + ); if(interact != NULL && ENTITY_CALLBACKS[interact->type].interact != NULL) { if(ENTITY_CALLBACKS[interact->type].interact(entity, interact)) return; } diff --git a/src/dusk/rpg/overworld/worldpos.c b/src/dusk/rpg/overworld/worldpos.c index 3cdd7100..494f1fbf 100644 --- a/src/dusk/rpg/overworld/worldpos.c +++ b/src/dusk/rpg/overworld/worldpos.c @@ -84,7 +84,7 @@ chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos) { return chunkTileIndex; } -chunkindex_t chunkPosToIndex(const chunkpos_t* pos) { +chunkindex_t chunkPosToIndex(const chunkpos_t *pos) { assertNotNull(pos, "Chunk position pointer cannot be NULL"); chunkindex_t chunkIndex = (chunkindex_t)( @@ -92,6 +92,20 @@ chunkindex_t chunkPosToIndex(const chunkpos_t* pos) { (pos->y * MAP_CHUNK_WIDTH) + pos->x ); - + return chunkIndex; +} + +void worldPosToFixed(const worldpos_t *pos, fixed_t *out) { + out[0] = fixedFromInt(pos->x); + out[1] = fixedFromInt(pos->y); + out[2] = fixedFromInt(pos->z); +} + +worldpos_t fixedToWorldPos(const fixed_t *position) { + return (worldpos_t){ + (worldunit_t)fixedToInt(position[0]), + (worldunit_t)fixedToInt(position[1]), + (worldunit_t)fixedToInt(position[2]) + }; } \ No newline at end of file diff --git a/src/dusk/rpg/overworld/worldpos.h b/src/dusk/rpg/overworld/worldpos.h index 87559cf1..c7e3087a 100644 --- a/src/dusk/rpg/overworld/worldpos.h +++ b/src/dusk/rpg/overworld/worldpos.h @@ -1,15 +1,13 @@ /** * Copyright (c) 2025 Dominic Masters - * + * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #pragma once #include "dusk.h" - -#define TILE_POS_MIN -128 -#define TILE_POS_MAX 127 +#include "util/fixed.h" #define CHUNK_WIDTH 16 #define CHUNK_HEIGHT CHUNK_WIDTH @@ -32,8 +30,6 @@ typedef uint32_t chunktileindex_t; typedef int32_t worldunits_t; typedef int32_t chunkunits_t; -typedef int8_t tileunit_t; - typedef struct worldpos_s { worldunit_t x, y, z; } worldpos_t; @@ -42,48 +38,60 @@ typedef struct chunkpos_t { chunkunit_t x, y, z; } chunkpos_t; -typedef struct tilepos_s { - tileunit_t x, y, z; -} tilepos_t; - /** * Compares two world positions for equality. - * + * * @param a The first world position. * @param b The second world position. * @return true if equal, false otherwise. */ bool_t worldPosIsEqual(const worldpos_t a, const worldpos_t b); -/** - * Converts a world position to a chunk position. - * - * @param worldPos The world position. - * @param out The output chunk position. - */ -void chunkPosToWorldPos(const chunkpos_t* chunkPos, worldpos_t* out); - /** * Converts a chunk position to a world position. - * + * + * @param chunkPos The chunk position. + * @param out The output world position. + */ +void chunkPosToWorldPos(const chunkpos_t *chunkPos, worldpos_t *out); + +/** + * Converts a world position to a chunk position. + * * @param worldPos The world position. * @param out The output chunk position. */ -void worldPosToChunkPos(const worldpos_t* worldPos, chunkpos_t* out); +void worldPosToChunkPos(const worldpos_t *worldPos, chunkpos_t *out); /** - * Converts a position in world-space to an index inside a chunk that the tile - * resides in. - * + * Converts a world position to the tile index within its chunk. + * * @param worldPos The world position. * @return The tile index within the chunk. */ -chunktileindex_t worldPosToChunkTileIndex(const worldpos_t* worldPos); +chunktileindex_t worldPosToChunkTileIndex(const worldpos_t *worldPos); /** - * Converts a chunk position to a world position. - * - * @param worldPos The world position. - * @param out The output chunk position. + * Converts a chunk position to a linear index. + * + * @param pos The chunk position. + * @return The linear chunk index. */ -chunkindex_t chunkPosToIndex(const chunkpos_t* pos); \ No newline at end of file +chunkindex_t chunkPosToIndex(const chunkpos_t *pos); + +/** + * Converts a world position to a fixed-point position. + * + * @param pos The integer world position. + * @param out The output fixed-point position (3 dimensions). + */ +void worldPosToFixed(const worldpos_t *pos, fixed_t *out); + +/** + * Converts the integer part of a fixed-point position to a world position. + * The fractional part is discarded. + * + * @param position The fixed-point position. + * @returns The integer tile world position. + */ +worldpos_t fixedToWorldPos(const fixed_t *position); \ No newline at end of file diff --git a/src/dusk/rpg/rpg.c b/src/dusk/rpg/rpg.c index 776f07fd..b003f60e 100644 --- a/src/dusk/rpg/rpg.c +++ b/src/dusk/rpg/rpg.c @@ -37,6 +37,13 @@ errorret_t rpgInit(void) { RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY; RPG_CAMERA.followEntity.followEntityId = ent->id; + // TEST: Add a test NPC. + uint8_t npcIndex = entityGetAvailable(); + assertTrue(npcIndex != 0xFF, "No available entity slots for NPC."); + entity_t *npc = &ENTITIES[npcIndex]; + entityInit(npc, ENTITY_TYPE_NPC); + worldPosToFixed(&(worldpos_t){ 3, 3, 0 }, npc->position); + // All Good! errorOk(); } diff --git a/src/dusk/rpg/rpgcamera.c b/src/dusk/rpg/rpgcamera.c index 0ab0c39e..16247a89 100644 --- a/src/dusk/rpg/rpgcamera.c +++ b/src/dusk/rpg/rpgcamera.c @@ -11,8 +11,6 @@ #include "rpg/overworld/map.h" #include "assert/assert.h" -#include "display/screen/screen.h" - rpgcamera_t RPG_CAMERA; void rpgCameraInit(void) { @@ -29,7 +27,7 @@ worldpos_t rpgCameraGetPosition(void) { if(entity->type == ENTITY_TYPE_NULL) { return (worldpos_t){ 0, 0, 0 }; } - return entity->position; + return fixedToWorldPos(entity->position); } default: @@ -39,15 +37,16 @@ worldpos_t rpgCameraGetPosition(void) { errorret_t rpgCameraUpdate(void) { if(!mapIsLoaded()) errorOk(); - - chunkpos_t chunkPos; + worldpos_t worldPos = rpgCameraGetPosition(); + chunkpos_t chunkPos; worldPosToChunkPos(&worldPos, &chunkPos); - + errorChain(mapPositionSet((chunkpos_t){ .x = chunkPos.x - (MAP_CHUNK_WIDTH / 2), .y = chunkPos.y - (MAP_CHUNK_HEIGHT / 2), .z = chunkPos.z - (MAP_CHUNK_DEPTH / 2) })); + errorOk(); } \ No newline at end of file diff --git a/src/dusk/rpg/rpgcamera.h b/src/dusk/rpg/rpgcamera.h index 824ca8c7..5a29161a 100644 --- a/src/dusk/rpg/rpgcamera.h +++ b/src/dusk/rpg/rpgcamera.h @@ -19,13 +19,11 @@ typedef struct { union { worldpos_t free; - + struct { uint8_t followEntityId; } followEntity; }; - - mat4 eye; } rpgcamera_t; extern rpgcamera_t RPG_CAMERA; diff --git a/src/dusk/scene/overworld/sceneoverworld.c b/src/dusk/scene/overworld/sceneoverworld.c index 6ec815c9..83dfe4d2 100644 --- a/src/dusk/scene/overworld/sceneoverworld.c +++ b/src/dusk/scene/overworld/sceneoverworld.c @@ -17,6 +17,7 @@ #include "rpg/overworld/map.h" #include "rpg/entity/entity.h" #include "rpg/rpgcamera.h" +#include "util/math.h" errorret_t sceneOverworldInit(scenedata_t *sceneData) { assertNotNull(sceneData, "Scene data cannot be null"); @@ -57,31 +58,53 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { ); errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj)); - // Camera Eye - float_t pixelsPerUnit = 16.0f; - float_t worldH = (float)SCREEN.height / pixelsPerUnit; - float_t z = (worldH * 0.5f) / tanf(fov * 0.5f); - worldpos_t worldPos = rpgCameraGetPosition(); - float_t offset = -16.0f; + // Camera view + { + vec3 target = { 0.5f, 0.5f, 0.5f }; + if(RPG_CAMERA.mode == RPG_CAMERA_MODE_FOLLOW_ENTITY) { + entity_t *followed = &ENTITIES[RPG_CAMERA.followEntity.followEntityId]; + if(followed->type != ENTITY_TYPE_NULL) { + float_t walkT = followed->animation == ENTITY_ANIM_WALK + ? fixedToFloat( + fixedDiv(followed->animTime, ENTITY_ANIM_WALK_DURATION) + ) + : 0.0f; + target[0] = mathLerp( + fixedToFloat(followed->position[0]), + fixedToFloat(followed->lastPosition[0]), + walkT + ) + 0.5f; + target[1] = mathLerp( + fixedToFloat(followed->position[1]), + fixedToFloat(followed->lastPosition[1]), + walkT + ) + 0.5f; + target[2] = mathLerp( + fixedToFloat(followed->position[2]), + fixedToFloat(followed->lastPosition[2]), + walkT + ) + 0.5f; + } + } else { + worldpos_t camPos = rpgCameraGetPosition(); + target[0] = (float_t)camPos.x + 0.5f; + target[1] = (float_t)camPos.y + 0.5f; + target[2] = (float_t)camPos.z + 0.5f; + } - vec3 worldPosVec = { - worldPos.x, - worldPos.y, - worldPos.z - }; - glm_vec3_add(worldPosVec, (vec3){ 0.5f, 0.5f, 0.5f }, worldPosVec); + float_t pixelsPerUnit = 16.0f; + float_t worldH = (float_t)SCREEN.height / pixelsPerUnit; + float_t eyeZ = (worldH * 0.5f) / tanf(fov * 0.5f); + float_t offset = -16.0f; - glm_lookat( - (vec3){ - worldPosVec[0], - worldPosVec[1] + offset, - worldPosVec[2] + z - }, - worldPosVec, - (vec3){ 0, 1, 0 }, // up - eye - ); - errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, eye)); + glm_lookat( + (vec3){ target[0], target[1] + offset, target[2] + eyeZ }, + target, + (vec3){ 0, 1, 0 }, + eye + ); + errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, eye)); + } // Chunks { @@ -119,10 +142,25 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData) { entity_t *ent = &ENTITIES[i]; if(ent->type == ENTITY_TYPE_NULL) continue; + float_t walkT = ent->animation == ENTITY_ANIM_WALK + ? fixedToFloat(fixedDiv(ent->animTime, ENTITY_ANIM_WALK_DURATION)) + : 0.0f; vec3 position = { - ent->position.x + ((float_t)ent->subPosition.x / TILE_POS_MAX), - ent->position.y + ((float_t)ent->subPosition.y / TILE_POS_MAX), - ent->position.z + ((float_t)ent->subPosition.z / TILE_POS_MAX) + 0.01f + mathLerp( + fixedToFloat(ent->position[0]), + fixedToFloat(ent->lastPosition[0]), + walkT + ), + mathLerp( + fixedToFloat(ent->position[1]), + fixedToFloat(ent->lastPosition[1]), + walkT + ), + mathLerp( + fixedToFloat(ent->position[2]), + fixedToFloat(ent->lastPosition[2]), + walkT + ) + 0.01f }; glm_vec3_copy(position, sprites[spriteCount].min); @@ -161,4 +199,5 @@ errorret_t sceneOverworldDispose(scenedata_t *sceneData) { errorOk(); -} \ No newline at end of file +} + diff --git a/src/dusk/scene/overworld/sceneoverworld.h b/src/dusk/scene/overworld/sceneoverworld.h index efd12f1c..56145e65 100644 --- a/src/dusk/scene/overworld/sceneoverworld.h +++ b/src/dusk/scene/overworld/sceneoverworld.h @@ -38,8 +38,9 @@ errorret_t sceneOverworldRender(scenedata_t *sceneData); /** * Disposes the overworld scene. - * + * * @param sceneData The scene data used for this scene. * @return An error if the dispose failed, or errorOk() if it succeeded. */ -errorret_t sceneOverworldDispose(scenedata_t *sceneData); \ No newline at end of file +errorret_t sceneOverworldDispose(scenedata_t *sceneData); + diff --git a/src/dusk/time/time.c b/src/dusk/time/time.c index ab4f20a5..77262378 100644 --- a/src/dusk/time/time.c +++ b/src/dusk/time/time.c @@ -31,22 +31,21 @@ void timeInit(void) { void timeUpdate(void) { #ifdef DUSK_TIME_DYNAMIC timeTickPlatform(); - TIME.dynamicDelta = timeGetDeltaPlatform(); - TIME.dynamicTime += TIME.dynamicDelta; + TIME.dynamicDelta = fixedFromFloat(timeGetDeltaPlatform()); + TIME.dynamicTime = fixedAdd(TIME.dynamicTime, TIME.dynamicDelta); TIME.dynamicUpdate = true; - assertTrue(TIME.dynamicDelta >= 0.0f, "Time delta is negative"); + assertTrue(TIME.dynamicDelta >= 0, "Time delta is negative"); - // Is within 1ms of a full step? - if(TIME.dynamicTime - TIME.lastNonDynamic >= DUSK_TIME_STEP * 0.999f) { + if(fixedSub(TIME.dynamicTime, TIME.lastNonDynamic) >= DUSK_TIME_STEP) { TIME.dynamicUpdate = false; TIME.lastNonDynamic = TIME.dynamicTime; TIME.delta = DUSK_TIME_STEP; - TIME.time += DUSK_TIME_STEP; + TIME.time = fixedAdd(TIME.time, DUSK_TIME_STEP); } #else TIME.delta = DUSK_TIME_STEP; - TIME.time += DUSK_TIME_STEP; + TIME.time = fixedAdd(TIME.time, DUSK_TIME_STEP); #endif // Print time in UTC style string diff --git a/src/dusk/time/time.h b/src/dusk/time/time.h index a4b38f25..09b007d5 100644 --- a/src/dusk/time/time.h +++ b/src/dusk/time/time.h @@ -8,9 +8,10 @@ #pragma once #include "timeepoch.h" #include "time/timeplatform.h" +#include "util/fixed.h" #ifndef DUSK_TIME_STEP - #define DUSK_TIME_STEP (16.0f / 1000.0f) + #define DUSK_TIME_STEP FIXED(16.0f / 1000.0f) #endif #ifdef DUSK_TIME_DYNAMIC @@ -23,14 +24,14 @@ #endif typedef struct { - float_t delta; - float_t time; + fixed_t delta; + fixed_t time; #ifdef DUSK_TIME_DYNAMIC - float_t lastNonDynamic; + fixed_t lastNonDynamic; bool_t dynamicUpdate; - float_t dynamicDelta; - float_t dynamicTime; + fixed_t dynamicDelta; + fixed_t dynamicTime; #endif } dusktime_t; diff --git a/src/dusk/ui/uifps.c b/src/dusk/ui/uifps.c index 0edf851c..8ce6816d 100644 --- a/src/dusk/ui/uifps.c +++ b/src/dusk/ui/uifps.c @@ -7,6 +7,7 @@ #include "uifps.h" #include "time/time.h" +#include "util/fixed.h" #include "display/spritebatch/spritebatch.h" #include "display/text/text.h" #include "display/screen/screen.h" @@ -26,18 +27,19 @@ errorret_t uiFPSDraw() { float_t fps = delta > 0 ? 1.0 / delta : 0.0; // Average FPS using exponential moving average - const float_t alpha = 0.1f; // Smoothing factor - if(UIFPS.fpsAverage == 0.0f) { - UIFPS.fpsAverage = fps; // Initialize average on first run + const float_t alpha = 0.1f; + if(UIFPS.fpsAverage == 0) { + UIFPS.fpsAverage = fixedFromFloat(fps); } else { - UIFPS.fpsAverage = alpha * fps + (1.0f - alpha) * UIFPS.fpsAverage; + float_t avg = alpha * fps + (1.0f - alpha) * fixedToFloat(UIFPS.fpsAverage); + UIFPS.fpsAverage = fixedFromFloat(avg); } snprintf( fpsText, sizeof(fpsText), "%.1f/%.1fms", - UIFPS.fpsAverage, + fixedToFloat(UIFPS.fpsAverage), delta * 1000.0f ); diff --git a/src/dusk/ui/uifps.h b/src/dusk/ui/uifps.h index 6f9c4592..7451f082 100644 --- a/src/dusk/ui/uifps.h +++ b/src/dusk/ui/uifps.h @@ -8,10 +8,11 @@ #pragma once #include "error/error.h" #include "time/timeepoch.h" +#include "util/fixed.h" typedef struct { dusktimeepoch_t lastTick; - float_t fpsAverage; + fixed_t fpsAverage; } uifps_t; extern uifps_t UIFPS; diff --git a/src/dusk/ui/uifullbox.c b/src/dusk/ui/uifullbox.c index 490306ef..5c5879f9 100644 --- a/src/dusk/ui/uifullbox.c +++ b/src/dusk/ui/uifullbox.c @@ -7,6 +7,7 @@ #include "uifullbox.h" #include "assert/assert.h" +#include "util/fixed.h" #include "util/memory.h" #include "display/screen/screen.h" #include "display/texture/texture.h" @@ -27,11 +28,11 @@ void uiFullboxInit(uifullbox_t *fullbox) { ); } -void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) { +void uiFullboxUpdate(uifullbox_t *fullbox, fixed_t delta) { assertNotNull(fullbox, "fullbox must not be NULL"); - if(fullbox->duration <= 0.0f || fullbox->time >= fullbox->duration) return; + if(fullbox->duration <= 0 || fullbox->time >= fullbox->duration) return; - fullbox->time += delta; + fullbox->time = fixedAdd(fullbox->time, delta); if(fullbox->time >= fullbox->duration) { fullbox->time = fullbox->duration; eventInvoke(&fullbox->onTransitionEnd, fullbox); @@ -39,10 +40,13 @@ void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta) { } static color_t uiFullboxGetColor(const uifullbox_t *fullbox) { - if(fullbox->duration <= 0.0f || fullbox->time >= fullbox->duration) { + if(fullbox->duration <= 0 || fullbox->time >= fullbox->duration) { return fullbox->toColor; } - float_t t = easingApply(fullbox->easing, fullbox->time / fullbox->duration); + float_t t = fixedToFloat(easingApply( + fullbox->easing, + fixedDiv(fullbox->time, fullbox->duration) + )); return color4b( (uint8_t)((float_t)fullbox->fromColor.r + ((float_t)fullbox->toColor.r - (float_t)fullbox->fromColor.r) * t), @@ -81,14 +85,14 @@ void uiFullboxTransition( uifullbox_t *fullbox, color_t from, color_t to, - float_t duration, + fixed_t duration, easingtype_t easing ) { assertNotNull(fullbox, "fullbox must not be NULL"); fullbox->fromColor = from; fullbox->toColor = to; fullbox->duration = duration; - fullbox->time = 0.0f; + fullbox->time = 0; fullbox->easing = easing; } diff --git a/src/dusk/ui/uifullbox.h b/src/dusk/ui/uifullbox.h index c1f172cc..300b8174 100644 --- a/src/dusk/ui/uifullbox.h +++ b/src/dusk/ui/uifullbox.h @@ -10,12 +10,13 @@ #include "display/color.h" #include "animation/easing.h" #include "event/event.h" +#include "util/fixed.h" typedef struct { color_t fromColor; color_t toColor; - float_t duration; - float_t time; + fixed_t duration; + fixed_t time; easingtype_t easing; eventcallback_t onTransitionEndCallbacks[4]; void *onTransitionEndUsers[4]; @@ -39,7 +40,7 @@ void uiFullboxInit(uifullbox_t *fullbox); * @param fullbox The fullbox to update. * @param delta Seconds elapsed since last update. */ -void uiFullboxUpdate(uifullbox_t *fullbox, float_t delta); +void uiFullboxUpdate(uifullbox_t *fullbox, fixed_t delta); /** * Renders the fullbox. Skipped entirely when the current alpha is zero. @@ -62,7 +63,7 @@ void uiFullboxTransition( uifullbox_t *fullbox, color_t from, color_t to, - float_t duration, + fixed_t duration, easingtype_t easing ); diff --git a/src/dusk/ui/uiloading.c b/src/dusk/ui/uiloading.c index ecf0b768..881a8bbf 100644 --- a/src/dusk/ui/uiloading.c +++ b/src/dusk/ui/uiloading.c @@ -7,6 +7,7 @@ #include "uiloading.h" #include "assert/assert.h" +#include "util/fixed.h" #include "util/memory.h" #include "display/text/text.h" #include "display/screen/screen.h" @@ -27,10 +28,10 @@ void uiLoadingInit(void) { ); } -void uiLoadingUpdate(float_t delta) { - if(UI_LOADING.duration <= 0.0f || UI_LOADING.time >= UI_LOADING.duration) +void uiLoadingUpdate(fixed_t delta) { + if(UI_LOADING.duration <= 0 || UI_LOADING.time >= UI_LOADING.duration) return; - UI_LOADING.time += delta; + UI_LOADING.time = fixedAdd(UI_LOADING.time, delta); if(UI_LOADING.time >= UI_LOADING.duration) { UI_LOADING.time = UI_LOADING.duration; eventInvoke(&UI_LOADING.onTransitionEnd, &UI_LOADING); @@ -39,10 +40,10 @@ void uiLoadingUpdate(float_t delta) { errorret_t uiLoadingDraw(void) { float_t alpha; - if(UI_LOADING.duration <= 0.0f || UI_LOADING.time >= UI_LOADING.duration) { + if(UI_LOADING.duration <= 0 || UI_LOADING.time >= UI_LOADING.duration) { alpha = UI_LOADING.toAlpha; } else { - float_t t = UI_LOADING.time / UI_LOADING.duration; + float_t t = fixedToFloat(fixedDiv(UI_LOADING.time, UI_LOADING.duration)); alpha = UI_LOADING.fromAlpha + (UI_LOADING.toAlpha - UI_LOADING.fromAlpha) * t; } @@ -71,7 +72,7 @@ static void uiLoadingTransition( UI_LOADING.fromAlpha = from; UI_LOADING.toAlpha = to; UI_LOADING.duration = UI_LOADING_FADE_DURATION; - UI_LOADING.time = 0.0f; + UI_LOADING.time = 0; eventInit( &UI_LOADING.onTransitionEnd, UI_LOADING.onTransitionEndCallbacks, diff --git a/src/dusk/ui/uiloading.h b/src/dusk/ui/uiloading.h index 60bf9414..f334f006 100644 --- a/src/dusk/ui/uiloading.h +++ b/src/dusk/ui/uiloading.h @@ -8,15 +8,16 @@ #pragma once #include "error/error.h" #include "event/event.h" +#include "util/fixed.h" -#define UI_LOADING_FADE_DURATION 0.5f +#define UI_LOADING_FADE_DURATION FIXED(0.5f) #define UI_LOADING_MARGIN 8.0f typedef struct { float_t fromAlpha; float_t toAlpha; - float_t duration; - float_t time; + fixed_t duration; + fixed_t time; eventcallback_t onTransitionEndCallbacks[4]; void *onTransitionEndUsers[4]; event_t onTransitionEnd; @@ -35,7 +36,7 @@ void uiLoadingInit(void); * * @param delta Seconds elapsed since last update. */ -void uiLoadingUpdate(float_t delta); +void uiLoadingUpdate(fixed_t delta); /** * Draws the loading indicator. No-op when fully transparent. diff --git a/src/dusk/ui/uiplayerpos.c b/src/dusk/ui/uiplayerpos.c index dd6a17a5..c1261877 100644 --- a/src/dusk/ui/uiplayerpos.c +++ b/src/dusk/ui/uiplayerpos.c @@ -22,17 +22,18 @@ errorret_t uiPlayerPosDraw() { } if(!player) errorOk(); + worldpos_t tilePos = fixedToWorldPos(player->position); chunkpos_t chunkPos; - worldPosToChunkPos(&player->position, &chunkPos); + worldPosToChunkPos(&tilePos, &chunkPos); char_t text[64]; snprintf( text, sizeof(text), "%d,%d,%d[%d,%d,%d]", - (int_t)player->position.x, - (int_t)player->position.y, - (int_t)player->position.z, + (int_t)tilePos.x, + (int_t)tilePos.y, + (int_t)tilePos.z, (int_t)chunkPos.x, (int_t)chunkPos.y, (int_t)chunkPos.z diff --git a/src/dusk/util/CMakeLists.txt b/src/dusk/util/CMakeLists.txt index 1beed776..95e34534 100644 --- a/src/dusk/util/CMakeLists.txt +++ b/src/dusk/util/CMakeLists.txt @@ -11,6 +11,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME} endian.c memory.c string.c + fixed.c math.c sort.c ref.c diff --git a/src/dusk/util/fixed.c b/src/dusk/util/fixed.c new file mode 100644 index 00000000..72e07eec --- /dev/null +++ b/src/dusk/util/fixed.c @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "fixed.h" + +fixed_t fixedFromInt(int32_t n) { + return (fixed_t)(n << FIXED_FRAC_BITS); +} + +fixed_t fixedFromFloat(float_t f) { + return (fixed_t)(f * (float_t)FIXED_SCALE); +} + +int32_t fixedToInt(fixed_t f) { + return (int32_t)(f >> FIXED_FRAC_BITS); +} + +float_t fixedToFloat(fixed_t f) { + return (float_t)f / (float_t)FIXED_SCALE; +} + +fixed_t fixedAdd(fixed_t a, fixed_t b) { + return (fixed_t)(a + b); +} + +fixed_t fixedSub(fixed_t a, fixed_t b) { + return (fixed_t)(a - b); +} + +fixed_t fixedMul(fixed_t a, fixed_t b) { + return (fixed_t)(((int64_t)a * (int64_t)b) >> FIXED_FRAC_BITS); +} + +fixed_t fixedDiv(fixed_t a, fixed_t b) { + return (fixed_t)(((int64_t)a << FIXED_FRAC_BITS) / (int64_t)b); +} + +fixed_t fixedNeg(fixed_t f) { + return (fixed_t)(-f); +} + +fixed_t fixedAbs(fixed_t f) { + return f < 0 ? (fixed_t)(-f) : f; +} + +fixed_t fixedFrac(fixed_t f) { + return (fixed_t)(f & FIXED_FRAC_MASK); +} + +fixed_t fixedFloor(fixed_t f) { + return (fixed_t)(f & ~(fixed_t)FIXED_FRAC_MASK); +} + +fixed_t fixedCeil(fixed_t f) { + return (fixed_t)((f + (fixed_t)FIXED_FRAC_MASK) & ~(fixed_t)FIXED_FRAC_MASK); +} + +fixed_t fixedLerp(fixed_t a, fixed_t b, fixed_t t) { + return fixedAdd(a, fixedMul(fixedSub(b, a), t)); +} diff --git a/src/dusk/util/fixed.h b/src/dusk/util/fixed.h new file mode 100644 index 00000000..d44dbc7b --- /dev/null +++ b/src/dusk/util/fixed.h @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +/** + * Q24.8 signed fixed-point: 24-bit whole part, 8-bit fractional part. + * Range: -8388608.0 to 8388607.996, resolution: 1/256 (~0.0039). + */ +typedef int32_t fixed_t; + +#define FIXED_FRAC_BITS 8 +#define FIXED_SCALE (1 << FIXED_FRAC_BITS) +#define FIXED_FRAC_MASK (FIXED_SCALE - 1) +#define FIXED_ZERO ((fixed_t)0) +#define FIXED_ONE ((fixed_t)FIXED_SCALE) +#define FIXED_HALF ((fixed_t)(FIXED_SCALE / 2)) +#define FIXED_MAX ((fixed_t)INT32_MAX) +#define FIXED_MIN ((fixed_t)INT32_MIN) + +/** Compile-time fixed-point literal from a float constant expression. */ +#define FIXED(x) ((fixed_t)((x) * FIXED_SCALE)) + +/** + * Creates a fixed-point value from an integer. + * + * @param n Integer to convert. + * @returns The fixed-point representation. + */ +fixed_t fixedFromInt(int32_t n); + +/** + * Creates a fixed-point value from a float. + * + * @param f Float to convert. + * @returns The fixed-point representation. + */ +fixed_t fixedFromFloat(float_t f); + +/** + * Converts a fixed-point value to an integer, rounding toward negative + * infinity. + * + * @param f Fixed-point value. + * @returns Integer part. + */ +int32_t fixedToInt(fixed_t f); + +/** + * Converts a fixed-point value to a float. + * + * @param f Fixed-point value. + * @returns Float representation. + */ +float_t fixedToFloat(fixed_t f); + +/** + * Adds two fixed-point values. + * + * @param a First operand. + * @param b Second operand. + * @returns a + b. + */ +fixed_t fixedAdd(fixed_t a, fixed_t b); + +/** + * Subtracts two fixed-point values. + * + * @param a First operand. + * @param b Second operand. + * @returns a - b. + */ +fixed_t fixedSub(fixed_t a, fixed_t b); + +/** + * Multiplies two fixed-point values. + * + * @param a First operand. + * @param b Second operand. + * @returns a * b. + */ +fixed_t fixedMul(fixed_t a, fixed_t b); + +/** + * Divides two fixed-point values. Divisor must not be zero. + * + * @param a Dividend. + * @param b Divisor. + * @returns a / b. + */ +fixed_t fixedDiv(fixed_t a, fixed_t b); + +/** + * Negates a fixed-point value. + * + * @param f Value to negate. + * @returns -f. + */ +fixed_t fixedNeg(fixed_t f); + +/** + * Returns the absolute value of a fixed-point number. + * + * @param f Value. + * @returns |f|. + */ +fixed_t fixedAbs(fixed_t f); + +/** + * Returns the fractional bits of a fixed-point value (always non-negative). + * + * @param f Value. + * @returns Fractional part in [0, FIXED_ONE). + */ +fixed_t fixedFrac(fixed_t f); + +/** + * Floors a fixed-point value toward negative infinity. + * + * @param f Value. + * @returns Largest fixed-point integer <= f. + */ +fixed_t fixedFloor(fixed_t f); + +/** + * Ceils a fixed-point value toward positive infinity. + * + * @param f Value. + * @returns Smallest fixed-point integer >= f. + */ +fixed_t fixedCeil(fixed_t f); + +/** + * Linearly interpolates between a and b by t, where t is in [0, FIXED_ONE]. + * + * @param a Start value. + * @param b End value. + * @param t Interpolation factor in [0, FIXED_ONE]. + * @returns a + (b - a) * t. + */ +fixed_t fixedLerp(fixed_t a, fixed_t b, fixed_t t);