diff --git a/CMakeLists.txt b/CMakeLists.txt index 7846e42..63cfa60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) if(NOT DEFINED DUSK_TARGET_SYSTEM) - #set(DUSK_TARGET_SYSTEM "psp") - set(DUSK_TARGET_SYSTEM "linux") + set(DUSK_TARGET_SYSTEM "psp") + # set(DUSK_TARGET_SYSTEM "linux") endif() # Prep cache diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index b9bd045..c4a5c7f 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -32,7 +32,6 @@ add_subdirectory(entity) add_subdirectory(event) add_subdirectory(item) add_subdirectory(locale) -add_subdirectory(physics) add_subdirectory(ui) add_subdirectory(util) add_subdirectory(world) \ No newline at end of file diff --git a/src/dusk/entity/entity.c b/src/dusk/entity/entity.c index 98b7f55..736631d 100644 --- a/src/dusk/entity/entity.c +++ b/src/dusk/entity/entity.c @@ -10,7 +10,7 @@ #include "util/memory.h" #include "world/world.h" #include "world/tiledata.h" -#include "physics/physics.h" +#include "time.h" entity_t ENTITIES[ENTITY_COUNT_MAX] = {0}; @@ -40,10 +40,8 @@ void entityLoad(entity_t *entity, const entity_t *source) { memoryZero(entity, sizeof(entity_t)); entity->type = source->type; - entity->x = source->x; - entity->y = source->y; - entity->vx = source->vx; - entity->vy = source->vy; + entity->x = (uint32_t)roundf(source->x / (float_t)TILE_WIDTH_HEIGHT); + entity->y = (uint32_t)roundf(source->y / (float_t)TILE_WIDTH_HEIGHT); entity->dir = source->dir; entity->id = source->id; @@ -60,73 +58,118 @@ void entityUpdate(entity_t *entity) { ); ENTITY_CALLBACKS[entity->type].update(entity); - - if(entity->vx == 0.0f && entity->vy == 0.0f) return; - - float_t newX = entity->x + entity->vx; - float_t newY = entity->y + entity->vy; - float_t halfTileWH = TILE_WIDTH_HEIGHT / 2.0f; - - // Because all hit detection is done assuming the entity is a circle, with - // its position centered, we need to precalc these; - float_t selfCircX = newX + halfTileWH; - float_t selfCircY = newY + halfTileWH; - float_t selfCircR = halfTileWH; - - // Check for collisions with tiles - float_t tileStartX = floorf((newX - halfTileWH) / TILE_WIDTH_HEIGHT); - float_t tileStartY = floorf((newY - halfTileWH) / TILE_WIDTH_HEIGHT); - float_t tileEndX = ceilf((newX + halfTileWH) / TILE_WIDTH_HEIGHT); - float_t tileEndY = ceilf((newY + halfTileWH) / TILE_WIDTH_HEIGHT); - - // For each tile - for(float_t y = tileStartY; y <= tileEndY; y += 1) { - for(float_t x = tileStartX; x <= tileEndX; x += 1) { - uint16_t tileX = (uint16_t)x; - uint16_t tileY = (uint16_t)y; - uint16_t chunkX = tileX / CHUNK_WIDTH; - uint16_t chunkY = tileY / CHUNK_HEIGHT; - chunk_t *chunk = chunkGetChunkAt(chunkX, chunkY); - if(chunk == NULL) continue; - - uint8_t chunkTileX = tileX % CHUNK_WIDTH; - uint8_t chunkTileY = tileY % CHUNK_HEIGHT; - tile_t tile = chunk->tilesBase[chunkTileY * CHUNK_WIDTH + chunkTileX]; - - collisionresult_t collision = physicsCheckCircleTile( - selfCircX, selfCircY, selfCircR, x, y, tile - ); - if(collision.hit && collision.depth > 0.01f) { - float_t slideX = collision.normalX * collision.depth; - float_t slideY = collision.normalY * collision.depth; - newX -= slideX; - newY -= slideY; - } - } + + if(entity->subX > 0) { + entity->subX -= entity->moveSpeed; + } else if(entity->subX < 0) { + entity->subX += entity->moveSpeed; } - // Check for collisions with other entities - entity_t *otherEntity = ENTITIES; + if(entity->subY > 0) { + entity->subY -= entity->moveSpeed; + } else if(entity->subY < 0) { + entity->subY += entity->moveSpeed; + } +} + +void entityMove(entity_t *entity, const uint8_t moveSpeed) { + assertNotNull(entity, "Entity pointer cannot be NULL"); + assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL"); + assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds"); + assertFalse( + entityIsMoving(entity), + "Entity is already moving, cannot move again" + ); + + int8_t x = 0, y = 0; + entityDirGetCoordinates(entity->dir, &x, &y); + + // entity in way? + entity_t *ent = entityGetAt(entity->x + x, entity->y + y); + if(ent != NULL) return; + + entity->x += x; + entity->y += y; + entity->subX = TILE_WIDTH_HEIGHT * -x; + entity->subY = TILE_WIDTH_HEIGHT * -y; + entity->moveSpeed = moveSpeed; +} + +void entityDirGetCoordinates( + const entitydir_t dir, + int8_t *x, int8_t *y +) { + assertNotNull(x, "X coordinate pointer cannot be NULL"); + assertNotNull(y, "Y coordinate pointer cannot be NULL"); + + switch(dir) { + case ENTITY_DIR_NORTH: + *x = 0; + *y = -1; + break; + + case ENTITY_DIR_SOUTH: + *x = 0; + *y = 1; + break; + + case ENTITY_DIR_EAST: + *x = 1; + *y = 0; + break; + + case ENTITY_DIR_WEST: + *x = -1; + *y = 0; + break; + + default: + assertUnreachable("Invalid entity direction"); + break; + } +} + +void entityTurn(entity_t *entity, const entitydir_t dir) { + assertNotNull(entity, "Entity pointer cannot be NULL"); + assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL"); + assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds"); + assertTrue( + dir >= ENTITY_DIR_SOUTH && dir <= ENTITY_DIR_NORTH, "Invalid direction" + ); + assertFalse( + entityIsMoving(entity), "Entity is already moving, cannot turn" + ); + + entity->dir = dir; +} + +bool_t entityIsMoving(const entity_t *entity) { + assertNotNull(entity, "Entity pointer cannot be NULL"); + assertTrue(entity->type != ENTITY_TYPE_NULL, "Entity type NULL"); + assertTrue(entity->type < ENTITY_TYPE_COUNT, "Entity type out of bounds"); + return entity->subX != 0 || entity->subY != 0; +} + +entity_t * entityGetAt( + const uint32_t tileX, + const uint32_t tileY +) { + entity_t *entity = ENTITIES; + do { - // Skip self and null entities - if(otherEntity == entity || otherEntity->type == ENTITY_TYPE_NULL) continue; + if(entity->type == ENTITY_TYPE_NULL) continue; + if(entity->x == tileX && entity->y == tileY) return entity; + } while((entity++) < &ENTITIES[ENTITY_COUNT_MAX - 1]); - float_t otherCircR = halfTileWH; + return NULL; +} - // We DONT use selfCircX/Y here because the other entity is ALSO a circle. - collisionresult_t collision = physicsCheckCircleCircle( - newX, newY, selfCircR, - otherEntity->x, otherEntity->y, otherCircR - ); - if(!collision.hit) continue; - - // Collision with entity detected. Slide out of collision. - float_t slideX = collision.normalX * collision.depth; - float_t slideY = collision.normalY * collision.depth; - newX -= slideX; - newY -= slideY; - } while(++otherEntity < ENTITIES + ENTITY_COUNT_MAX); - - entity->x = newX; - entity->y = newY; +float_t entityDirToAngle(const entitydir_t dir) { + switch(dir) { + case ENTITY_DIR_NORTH: return (M_PI_2); + case ENTITY_DIR_SOUTH: return -(M_PI_2); + case ENTITY_DIR_EAST: return 0; + case ENTITY_DIR_WEST: return (M_PI); + default: return 0; // Should never happen + } } \ No newline at end of file diff --git a/src/dusk/entity/entity.h b/src/dusk/entity/entity.h index ed91c07..255b8ed 100644 --- a/src/dusk/entity/entity.h +++ b/src/dusk/entity/entity.h @@ -10,6 +10,8 @@ #include "npc.h" #define ENTITY_COUNT_MAX 32 +#define ENTITY_TURN_DURATION 0.075f // Duration for turning in seconds +#define ENTITY_MOVE_DURATION 0.1f // Duration for moving 1 tile, in seconds. typedef enum { ENTITY_DIR_SOUTH = 0, @@ -32,11 +34,14 @@ typedef enum { typedef struct _entity_t { uint32_t id;// Completely unique ID for this entity. - float_t x, y; - float_t vx, vy; + uint32_t x, y; + int8_t subX, subY; + uint8_t moveSpeed; + entitytype_t type; entitydir_t dir; + union { npc_t npc; playerentity_t player; @@ -73,4 +78,53 @@ void entityUpdate(entity_t *entity); * @param dir The entity direction to convert. * @return The angle corresponding to the entity direction. */ -float_t entityDirToAngle(const entitydir_t dir); \ No newline at end of file +float_t entityDirToAngle(const entitydir_t dir); + +/** + * Moves the entity by the specified x and y offsets. + * + * @param entity Pointer to the entity to move. + * @param moveSpeed The speed at which to move the entity. + */ +void entityMove(entity_t *entity, const uint8_t moveSpeed); + +/** + * Gets the coordinates of the entity direction. + * + * @param dir The entity direction to get coordinates for. + * @param x Pointer to store the x coordinate. + * @param y Pointer to store the y coordinate. + */ +void entityDirGetCoordinates( + const entitydir_t dir, + int8_t *x, int8_t *y +); + +/** + * Turns the entity to face the specified direction. + * + * @param entity Pointer to the entity to turn. + * @param dir The direction to turn the entity to. + */ +void entityTurn(entity_t *entity, const entitydir_t dir); + +/** + * Returns whether or not an entity is currently moving. + * + * @param entity Pointer to the entity to check. + * @return True if the entity is moving, false otherwise. + */ +bool_t entityIsMoving(const entity_t *entity); + +/** + * Gets the entity at the specified tile coordinates. + * + * @param tileX The x coordinate of the tile to get the entity from. + * @param tileY The y coordinate of the tile to get the entity from. + * @return Pointer to the entity at the specified coordinates, or NULL if no + * entity exists there. + */ +entity_t *entityGetAt( + const uint32_t tileX, + const uint32_t tileY +); \ No newline at end of file diff --git a/src/dusk/entity/player.c b/src/dusk/entity/player.c index 8a51de8..4208ebf 100644 --- a/src/dusk/entity/player.c +++ b/src/dusk/entity/player.c @@ -10,7 +10,6 @@ #include "input.h" #include "display/render.h" #include "world/world.h" -#include "physics/physics.h" #include "ui/uitextbox.h" @@ -41,117 +40,55 @@ void playerEntityUpdate(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); assertTrue(entity->type == ENTITY_TYPE_PLAYER, "Entity type must be PLAYER"); - if(UI_TEXTBOX.visible) { - entity->vx = entity->vy = 0; - return; - } + // TODO: make this just a method somewhere. + if(UI_TEXTBOX.visible) return; + if(entityIsMoving(entity)) return; + + const uint8_t moveSpeed = inputIsDown(INPUT_BIND_CANCEL) ? PLAYER_SPEED_RUN : PLAYER_SPEED_WALK; if(inputIsDown(INPUT_BIND_UP)) { - if(inputIsDown(INPUT_BIND_LEFT)) { - entity->vx = -PLAYER_MOVE_SPEED_XY; - entity->vy = -PLAYER_MOVE_SPEED_XY; - - if(entity->dir != ENTITY_DIR_NORTH && entity->dir != ENTITY_DIR_WEST) { - entity->dir = ENTITY_DIR_NORTH; - } - } else if(inputIsDown(INPUT_BIND_RIGHT)) { - entity->vx = PLAYER_MOVE_SPEED_XY; - entity->vy = -PLAYER_MOVE_SPEED_XY; - - if(entity->dir != ENTITY_DIR_NORTH && entity->dir != ENTITY_DIR_EAST) { - entity->dir = ENTITY_DIR_NORTH; - } - } else { - entity->vy = -PLAYER_MOVE_SPEED; - entity->vx = 0; - entity->dir = ENTITY_DIR_NORTH; + if(entity->dir != ENTITY_DIR_NORTH) { + entityTurn(entity, ENTITY_DIR_NORTH); + return; } + entityMove(entity, moveSpeed); + return; + } else if(inputIsDown(INPUT_BIND_DOWN)) { - if(inputIsDown(INPUT_BIND_LEFT)) { - entity->vx = -PLAYER_MOVE_SPEED_XY; - entity->vy = PLAYER_MOVE_SPEED_XY; - - if(entity->dir != ENTITY_DIR_SOUTH && entity->dir != ENTITY_DIR_WEST) { - entity->dir = ENTITY_DIR_SOUTH; - } - } else if(inputIsDown(INPUT_BIND_RIGHT)) { - entity->vx = PLAYER_MOVE_SPEED_XY; - entity->vy = PLAYER_MOVE_SPEED_XY; - - if(entity->dir != ENTITY_DIR_SOUTH && entity->dir != ENTITY_DIR_EAST) { - entity->dir = ENTITY_DIR_SOUTH; - } - } else { - entity->vy = PLAYER_MOVE_SPEED; - entity->vx = 0; - entity->dir = ENTITY_DIR_SOUTH; + if(entity->dir != ENTITY_DIR_SOUTH) { + entityTurn(entity, ENTITY_DIR_SOUTH); + return; } + + entityMove(entity, moveSpeed); + return; } else if(inputIsDown(INPUT_BIND_LEFT)) { - entity->vx = -PLAYER_MOVE_SPEED; - entity->vy = 0; - entity->dir = ENTITY_DIR_WEST; + if(entity->dir != ENTITY_DIR_WEST) { + entityTurn(entity, ENTITY_DIR_WEST); + return; + } + entityMove(entity, moveSpeed); + return; + } else if(inputIsDown(INPUT_BIND_RIGHT)) { - entity->vx = PLAYER_MOVE_SPEED; - entity->vy = 0; - entity->dir = ENTITY_DIR_EAST; - } else { - entity->vx = 0; - entity->vy = 0; + if(entity->dir != ENTITY_DIR_EAST) { + entityTurn(entity, ENTITY_DIR_EAST); + return; + } + + entityMove(entity, moveSpeed); + return; } // Interact if(inputPressed(INPUT_BIND_ACTION)) { - entity_t *other = ENTITIES; - do { - if(other == entity || other->type == ENTITY_TYPE_NULL) { - other++; - continue; - } + int8_t x, y; + entityDirGetCoordinates(entity->dir, &x, &y); + entity_t *ent = entityGetAt(entity->x + x, entity->y + y); - // Is the other entity interactable? - if(ENTITY_CALLBACKS[other->type].interact == NULL) { - other++; - continue; - } - - float_t distanceX = other->x - entity->x; - float_t distanceY = other->y - entity->y; - float_t distance = sqrtf(distanceX * distanceX + distanceY * distanceY); - - if(distance > PLAYER_INTERACT_RANGE) { - other++; - continue; - } - - // Get angle - float_t angle = atan2f(distanceY, distanceX); - while(angle < 0) angle += M_PI; - float_t selfAngle = entityDirToAngle(entity->dir); - while(selfAngle < 0) selfAngle += M_PI; - - // Check if angle is within range - float_t angleDiff = angle - selfAngle; - if(angleDiff > (M_PI_2)) angleDiff -= M_PI; - if(angleDiff < -M_PI_2) angleDiff += M_PI; - if(fabsf(angleDiff) > PLAYER_INTERACT_ANGLE) { - other++; - continue; - } - - ENTITY_CALLBACKS[other->type].interact(entity, other); - entity->vx = 0; - entity->vy = 0; - other++; - } while(other != ENTITIES + ENTITY_COUNT_MAX); - } -} - -float_t entityDirToAngle(const entitydir_t dir) { - switch(dir) { - case ENTITY_DIR_NORTH: return (M_PI_2); - case ENTITY_DIR_SOUTH: return -(M_PI_2); - case ENTITY_DIR_EAST: return 0; - case ENTITY_DIR_WEST: return (M_PI); - default: return 0; // Should never happen + if(ent != NULL && ENTITY_CALLBACKS[ent->type].interact != NULL) { + assertTrue(ent->type < ENTITY_TYPE_COUNT, "Entity type out of bounds"); + ENTITY_CALLBACKS[ent->type].interact(entity, ent); + } } } \ No newline at end of file diff --git a/src/dusk/entity/player.h b/src/dusk/entity/player.h index 59e8352..570f423 100644 --- a/src/dusk/entity/player.h +++ b/src/dusk/entity/player.h @@ -9,6 +9,9 @@ #include "dusk.h" #include "item/inventory.h" +#define PLAYER_SPEED_WALK 1 +#define PLAYER_SPEED_RUN 2 + typedef struct _entity_t entity_t; typedef struct { @@ -16,10 +19,10 @@ typedef struct { } playerentity_t; #define PLAYER_ENTITY_ID (UINT32_MAX-1) -#define PLAYER_MOVE_SPEED 1.0f -#define PLAYER_MOVE_SPEED_XY 0.7f +// #define PLAYER_MOVE_SPEED 1.5f +// #define PLAYER_MOVE_SPEED_XY (PLAYER_MOVE_SPEED * 1.4142f) #define PLAYER_INTERACT_RANGE (TILE_WIDTH_HEIGHT + (TILE_WIDTH_HEIGHT / 3)) -#define PLAYER_INTERACT_ANGLE 0.68359375f +// #define PLAYER_INTERACT_ANGLE 0.68359375f extern inventory_t PLAYER_INVENTORY; diff --git a/src/dusk/game.c b/src/dusk/game.c index cafc159..9ee1ead 100644 --- a/src/dusk/game.c +++ b/src/dusk/game.c @@ -38,6 +38,7 @@ void gameUpdate(void) { // issues float_t timeSinceLastTick = TIME.time - TIME.lastTick; while(timeSinceLastTick >= DUSK_TIME_STEP) { + sceneUpdate(); uiTextboxUpdate(); eventUpdate(); diff --git a/src/dusk/physics/CMakeLists.txt b/src/dusk/physics/CMakeLists.txt deleted file mode 100644 index 0f79ab8..0000000 --- a/src/dusk/physics/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright (c) 2025 Dominic Masters -# -# This software is released under the MIT License. -# https://opensource.org/licenses/MIT - -# Sources -target_sources(${DUSK_TARGET_NAME} - PRIVATE - physics.c -) \ No newline at end of file diff --git a/src/dusk/physics/physics.c b/src/dusk/physics/physics.c deleted file mode 100644 index 55130dc..0000000 --- a/src/dusk/physics/physics.c +++ /dev/null @@ -1,293 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "physics.h" -#include "world/tiledata.h" - -collisionresult_t physicsCheckCircleCircle( - float_t circle0x, float_t circle0y, float_t circle0r, - float_t circle1x, float_t circle1y, float_t circle1r -) { - collisionresult_t result; - // Compute vector between centers - float_t dx = circle1x - circle0x; - float_t dy = circle1y - circle0y; - - // Distance squared between centers - float_t distSq = (dx * dx) + (dy * dy); - - // Sum of radii - float_t rSum = circle0r + circle1r; - float_t rSumSq = rSum * rSum; - - if(distSq > rSumSq) { - // No collision - result.hit = false; - return result; - } - - // Collision: calculate normal and penetration depth - float_t dist = sqrtf(distSq); - - // If centers are the same, pick arbitrary normal (1,0) - if(dist == 0) { - result.normalX = 1; - result.normalY = 0; - result.depth = rSum; - } else { - // Normalized direction from circle0 to circle1 - result.normalX = dx / dist; - result.normalY = dy / dist; - // Penetration depth = sum of radii - distance - result.depth = rSum - dist; - } - result.hit = true; - return result; -} - -collisionresult_t physicsCheckCircleAABB( - float_t circleX, float_t circleY, float_t circleR, - float_t aabbX, float_t aabbY, - float_t aabbWidth, float_t aabbHeight -) { - collisionresult_t result; - - // Find the closest point on the AABB to the circle center - float_t closestX = fmaxf( - aabbX, fminf(circleX, aabbX + aabbWidth) - ); - float_t closestY = fmaxf( - aabbY, fminf(circleY, aabbY + aabbHeight) - ); - - // Vector from circle center to closest point - float_t dx = closestX - circleX; - float_t dy = closestY - circleY; - - // Distance squared from circle center to closest point - float_t distSq = (dx * dx) + (dy * dy); - - // Check if distance is less than radius squared - if(distSq > (circleR * circleR)) { - result.hit = false; - return result; - } - - // Collision: calculate normal and penetration depth - float_t dist = sqrtf(distSq); - - if(dist <= 1) { - // Circle center is at the AABB corner - result.normalX = 1.0f; - result.normalY = 0.0f; - result.depth = circleR; - } else { - // Normalized direction from circle center to closest point - result.normalX = dx / dist; - result.normalY = dy / dist; - // Penetration depth = radius - distance - result.depth = circleR - dist; - } - - result.hit = true; - return result; -} - -void physicsClosestPointOnSegment( - float_t ax, float_t ay, - float_t bx, float_t by, - float_t px, float_t py, - float_t *outX, float_t *outY -) { - float_t abx = bx - ax; - float_t aby = by - ay; - float_t apx = px - ax; - float_t apy = py - ay; - - float_t abLenSq = (abx * abx) + (aby * aby); - - if(abLenSq == 0) { - *outX = ax; - *outY = ay; - return; - } - - float_t t = apx * abx + apy * aby; - t /= abLenSq; - - if(t < 0) t = 0; - if(t > 1) t = 1; - - *outX = ax + (abx * t); - *outY = ay + (aby * t); -} - -bool_t physicsIsPointInTriangle( - float_t px, float_t py, - float_t ax, float_t ay, - float_t bx, float_t by, - float_t cx, float_t cy -) { - float_t abx = bx - ax; - float_t aby = by - ay; - float_t bcx = cx - bx; - float_t bcy = cy - by; - float_t cax = ax - cx; - float_t cay = ay - cy; - - float_t apx = px - ax; - float_t apy = py - ay; - float_t bpx = px - bx; - float_t bpy = py - by; - float_t cpx = px - cx; - float_t cpy = py - cy; - - float_t cross1 = (abx * apy) - (aby * apx); - float_t cross2 = (bcx * bpy) - (bcy * bpx); - float_t cross3 = (cax * cpy) - (cay * cpx); - - bool_t hasNeg = ( - (cross1 < 0) || - (cross2 < 0) || - (cross3 < 0) - ); - - bool_t hasPos = ( - (cross1 > 0) || - (cross2 > 0) || - (cross3 > 0) - ); - - return !(hasNeg && hasPos); -} - -collisionresult_t physicsCheckCircleTriangle( - float_t circleX, float_t circleY, float_t circleR, - float_t triX0, float_t triY0, - float_t triX1, float_t triY1, - float_t triX2, float_t triY2 -) { - collisionresult_t result = { .hit = false }; - float_t vx[3] = { triX0, triX1, triX2 }; - float_t vy[3] = { triY0, triY1, triY2 }; - - float_t closestX = 0; - float_t closestY = 0; - float_t minDistSq = FLT_MAX; - - for(uint8_t i = 0; i < 3; ++i) { - uint8_t j = (i + 1) % 3; - - float_t testX, testY; - physicsClosestPointOnSegment( - vx[i], vy[i], vx[j], vy[j], - circleX, circleY, &testX, &testY - ); - - float_t dx = circleX - testX; - float_t dy = circleY - testY; - float_t distSq = (dx * dx) + (dy * dy); - - if(distSq < minDistSq) { - minDistSq = distSq; - closestX = testX; - closestY = testY; - result.normalX = dx; - result.normalY = dy; - } - } - - float_t dist = sqrtf(minDistSq); - float_t invDist = ( - (dist != 0) ? - (1.0f / dist) : - 1.0f - ); - - result.normalX = -result.normalX * invDist; - result.normalY = -result.normalY * invDist; - - if(physicsIsPointInTriangle( - circleX, circleY, vx[0], vy[0], vx[1], vy[1], vx[2], vy[2] - )) { - result.hit = true; - result.depth = circleR - dist; - return result; - } - - if(dist < circleR) { - result.hit = true; - result.depth = circleR - dist; - } - - return result; -} - -collisionresult_t physicsCheckCircleTile( - float_t circleX, float_t circleY, float_t circleR, - float_t tileX, float_t tileY, tile_t tile -) { - collisionresult_t result; - - #define tw (TILE_WIDTH_HEIGHT) - #define th (TILE_WIDTH_HEIGHT) - #define lx (tileX * tw) - #define ty (tileY * th) - #define rx (lx + tw) - #define by (ty + th) - - switch(TILE_META_DATA[tile].solidType) { - case TILE_SOLID_FULL: - result = physicsCheckCircleAABB( - circleX, circleY, circleR, - lx, ty, - tw, th - ); - break; - - case TILE_SOLID_TRIANGLE_TOP_RIGHT: - result = physicsCheckCircleTriangle( - circleX, circleY, circleR, - rx, by, - rx, ty, - lx, ty - ); - break; - - case TILE_SOLID_TRIANGLE_TOP_LEFT: - result = physicsCheckCircleTriangle( - circleX, circleY, circleR, - lx, by, - lx, ty, - rx, ty - ); - break; - - case TILE_SOLID_TRIANGLE_BOTTOM_RIGHT: - result = physicsCheckCircleTriangle( - circleX, circleY, circleR, - rx, ty, - rx, by, - lx, by - ); - break; - case TILE_SOLID_TRIANGLE_BOTTOM_LEFT: - result = physicsCheckCircleTriangle( - circleX, circleY, circleR, - lx, ty, - lx, by, - rx, by - ); - break; - - default: - result.hit = false; - break; - } - - return result; -} \ No newline at end of file diff --git a/src/dusk/physics/physics.h b/src/dusk/physics/physics.h deleted file mode 100644 index ba61244..0000000 --- a/src/dusk/physics/physics.h +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "world/tile.h" - -typedef struct { - bool_t hit; - float_t normalX, normalY; - float_t depth; -} collisionresult_t; - -/** - * Check for collision between two circles. - * - * @param circle0x X coordinate of the first circle's center. - * @param circle0y Y coordinate of the first circle's center. - * @param circle0r Radius of the first circle. - * @param circle1x X coordinate of the second circle's center. - * @param circle1y Y coordinate of the second circle's center. - * @param circle1r Radius of the second circle. - * @return A collisionresult_t structure containing collision information. - */ -collisionresult_t physicsCheckCircleCircle( - float_t circle0x, float_t circle0y, float_t circle0r, - float_t circle1x, float_t circle1y, float_t circle1r -); - -/** - * Check for collision between a circle and an axis-aligned bounding box (AABB). - * - * @param circleX X coordinate of the circle's center. - * @param circleY Y coordinate of the circle's center. - * @param circleR Radius of the circle. - * @param aabb X coordinate of the AABB's top-left corner. - * @param aabbY Y coordinate of the AABB's top-left corner. - * @param aabbWidth Width of the AABB. - * @param aabbHeight Height of the AABB. - * @return A collisionresult_t structure containing collision information. - */ -collisionresult_t physicsCheckCircleAABB( - float_t circleX, float_t circleY, float_t circleR, - float_t aabb, float_t aabbY, - float_t aabbWidth, float_t aabbHeight -); - -/** - * Calculate the closest point on a line segment to a point. - * - * @param ax X coordinate of the first endpoint of the segment. - * @param ay Y coordinate of the first endpoint of the segment. - * @param bx X coordinate of the second endpoint of the segment. - * @param by Y coordinate of the second endpoint of the segment. - * @param px X coordinate of the point. - * @param py Y coordinate of the point. - * @param outX Pointer to store the X coordinate of the closest point. - * @param outY Pointer to store the Y coordinate of the closest point. - */ -void physicsClosestPointOnSegment( - float_t ax, float_t ay, - float_t bx, float_t by, - float_t px, float_t py, - float_t *outX, float_t *outY -); - -/** - * Check if a point is inside a triangle defined by three vertices. - * - * @param px X coordinate of the point. - * @param py Y coordinate of the point. - * @param x0 X coordinate of the first vertex of the triangle. - * @param y0 Y coordinate of the first vertex of the triangle. - * @param x1 X coordinate of the second vertex of the triangle. - * @param y1 Y coordinate of the second vertex of the triangle. - * @param x2 X coordinate of the third vertex of the triangle. - * @param y2 Y coordinate of the third vertex of the triangle. - * @return true if the point is inside the triangle, false otherwise. - */ -bool_t physicsIsPointInTriangle( - float_t px, float_t py, - float_t x0, float_t y0, - float_t x1, float_t y1, - float_t x2, float_t y2 -); - -/** - * Check for collision between a circle and a triangle. - * - * @param circleX X coordinate of the circle's center. - * @param circleY Y coordinate of the circle's center. - * @param circleR Radius of the circle. - * @param triX0 X coordinate of the first vertex of the triangle. - * @param triY0 Y coordinate of the first vertex of the triangle. - * @param triX1 X coordinate of the second vertex of the triangle. - * @param triY1 Y coordinate of the second vertex of the triangle. - * @param triX2 X coordinate of the third vertex of the triangle. - * @param triY2 Y coordinate of the third vertex of the triangle. - * @return A collisionresult_t structure containing collision information. - */ -collisionresult_t physicsCheckCircleTriangle( - float_t circleX, float_t circleY, float_t circleR, - float_t triX0, float_t triY0, - float_t triX1, float_t triY1, - float_t triX2, float_t triY2 -); - -/** - * Check for collision between a circle and a tile. - * - * @param circleX X coordinate of the circle's center. - * @param circleY Y coordinate of the circle's center. - * @param circleR Radius of the circle. - * @param tileX X coordinate of the tile's top-left corner. - * @param tileY Y coordinate of the tile's top-left corner. - * @param tile The tile to check against. - * @return A collisionresult_t structure containing collision information. - */ -collisionresult_t physicsCheckCircleTile( - float_t circleX, float_t circleY, float_t circleR, - float_t tileX, float_t tileY, tile_t tile -); \ No newline at end of file diff --git a/src/dusk/time.c b/src/dusk/time.c index a649f72..6056a42 100644 --- a/src/dusk/time.c +++ b/src/dusk/time.c @@ -13,15 +13,25 @@ dusktime_t TIME; void timeInit(void) { memoryZero(&TIME, sizeof(TIME)); + + // Set these to something non-zero. + TIME.lastTick = DUSK_TIME_STEP; + TIME.delta = TIME.realDelta = DUSK_TIME_STEP; + TIME.realTime = TIME.time = DUSK_TIME_STEP * 2; } void timeUpdate(void) { + TIME.realDelta = timeDeltaGet(); + #if DUSK_TIME_DYNAMIC - TIME.delta = timeDeltaGet(); + TIME.delta = TIME.realDelta; #else TIME.delta = DUSK_TIME_PLATFORM_STEP; #endif + + assertTrue(TIME.delta >= 0.0f, "Time delta is negative"); + assertTrue(TIME.realDelta >= 0.0f, "Real time delta is negative"); TIME.time += TIME.delta; - assertTrue(TIME.delta >= 0.0f, "Time delta is negative"); + TIME.realTime += TIME.realDelta; } \ No newline at end of file diff --git a/src/dusk/time.h b/src/dusk/time.h index ae9796d..7c878c8 100644 --- a/src/dusk/time.h +++ b/src/dusk/time.h @@ -12,6 +12,8 @@ typedef struct { float_t delta; float_t lastTick; float_t time; + float_t realDelta; + float_t realTime; } dusktime_t; extern dusktime_t TIME; diff --git a/src/dusk/world/overworld.c b/src/dusk/world/overworld.c index 66c1035..de51e28 100644 --- a/src/dusk/world/overworld.c +++ b/src/dusk/world/overworld.c @@ -47,8 +47,8 @@ void overworldUpdate() { entity->type == ENTITY_TYPE_PLAYER, "First entity must be player" ); - OVERWORLD_CAMERA_X = (uint32_t)floorf(entity->x); - OVERWORLD_CAMERA_Y = (uint32_t)floorf(entity->y); + OVERWORLD_CAMERA_X = entity->x * TILE_WIDTH_HEIGHT + entity->subX; + OVERWORLD_CAMERA_Y = entity->y * TILE_WIDTH_HEIGHT + entity->subY; uint16_t x, y; uint16_t halfWidth, halfHeight; diff --git a/src/duskpsp/CMakeLists.txt b/src/duskpsp/CMakeLists.txt index 822d982..d7dfc0a 100644 --- a/src/duskpsp/CMakeLists.txt +++ b/src/duskpsp/CMakeLists.txt @@ -20,7 +20,8 @@ target_compile_definitions(${DUSK_TARGET_NAME} RENDER_HEIGHT=272 RENDER_WINDOW_WIDTH_DEFAULT=480 RENDER_WINDOW_HEIGHT_DEFAULT=272 - DUSK_TIME_DYNAMIC=0 + # DUSK_TIME_DYNAMIC=0 + DUSK_TIME_DYNAMIC=1 ) # Includes diff --git a/src/dusksdl2/display/overworld/renderoverworld.c b/src/dusksdl2/display/overworld/renderoverworld.c index c308558..f9cc155 100644 --- a/src/dusksdl2/display/overworld/renderoverworld.c +++ b/src/dusksdl2/display/overworld/renderoverworld.c @@ -44,16 +44,18 @@ void renderOverworldDraw(void) { meshDraw(&chunk->meshBase, -1, -1); } - for(uint8_t i = 0; i < ENTITY_COUNT_MAX; i++) { entity_t *entity = &ENTITIES[i]; if(entity->type == ENTITY_TYPE_NULL) continue; + float_t x = (entity->x * TILE_WIDTH_HEIGHT) + entity->subX; + float_t y = (entity->y * TILE_WIDTH_HEIGHT) + entity->subY; + // Draw the entity spriteBatchPush( NULL, - floorf(entity->x), floorf(entity->y), - floorf(entity->x + TILE_WIDTH_HEIGHT), floorf(entity->y + TILE_WIDTH_HEIGHT), + x, y, + x + TILE_WIDTH_HEIGHT, y + TILE_WIDTH_HEIGHT, 0xFF, 0x00, 0xFF, 0XFF, 0.0f, 0.0f, 1.0f, 1.0f ); diff --git a/src/dusksdl2/display/renderoverworld.h b/src/dusksdl2/display/renderoverworld.h deleted file mode 100644 index f0a9ce0..0000000 --- a/src/dusksdl2/display/renderoverworld.h +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once \ No newline at end of file diff --git a/src/dusksdl2/display/ui/renderfps.c b/src/dusksdl2/display/ui/renderfps.c index 94a98e7..7c4fdce 100644 --- a/src/dusksdl2/display/ui/renderfps.c +++ b/src/dusksdl2/display/ui/renderfps.c @@ -9,24 +9,38 @@ #include "display/render.h" #include "display/ui/rendertext.h" #include "time.h" +#include "game.h" + +float_t RENDER_FPS_AVG = -1.0f; +float_t RENDER_TPS_AVG = -1.0f; void renderFPSDraw(void) { - float_t fps = 1.0f / TIME.delta; + + if(TIME.delta > 0) { + float_t fps = 1.0f / TIME.realDelta; - char_t buffer[32]; - snprintf(buffer, sizeof(buffer), "%.1f FPS", fps); + if(RENDER_FPS_AVG == -1.0f) { + RENDER_FPS_AVG = fps; + } else { + RENDER_FPS_AVG = (RENDER_FPS_AVG + fps) / 2.0f; + } + } + + if(TIME.time != TIME.lastTick) { + float_t timeSinceLastTick = TIME.realTime - TIME.lastTick; + float_t tps = 1.0f / timeSinceLastTick; + + if(RENDER_TPS_AVG == -1.0f) { + RENDER_TPS_AVG = tps; + } else { + RENDER_TPS_AVG = (RENDER_TPS_AVG + tps) / 2.0f; + } + } + + char_t buffer[64]; + snprintf(buffer, sizeof(buffer), "%.1f/%.1f", RENDER_FPS_AVG, RENDER_TPS_AVG); int32_t width, height; renderTextMeasure(buffer, &width, &height); - - if(fps >= 50.0f) { - // Green - renderTextDraw(RENDER_WIDTH - width, 0, buffer, 0x00, 0xFF, 0x00); - } else if(fps >= 30.0f) { - // Yellow - renderTextDraw(RENDER_WIDTH - width, 0, buffer, 0xFF, 0xFF, 0x00); - } else { - // Red - renderTextDraw(RENDER_WIDTH - width, 0, buffer, 0xFF, 0x00, 0x00); - } + renderTextDraw(RENDER_WIDTH - width, 0, buffer, 0x00, 0xFF, 0x00); } \ No newline at end of file diff --git a/src/dusksdl2/time.c b/src/dusksdl2/time.c index a656061..c9a0dd1 100644 --- a/src/dusksdl2/time.c +++ b/src/dusksdl2/time.c @@ -8,14 +8,12 @@ #include "time.h" #include "dusksdl2.h" -#if DUSK_TIME_DYNAMIC - uint32_t TIME_LAST = 0; +uint32_t TIME_LAST = 0; - float_t timeDeltaGet(void) { - // Get the current time in milliseconds - uint32_t currentTime = SDL_GetTicks(); - float_t delta = (currentTime - TIME_LAST) / 1000.0f; - TIME_LAST = currentTime; - return delta; - } -#endif \ No newline at end of file +float_t timeDeltaGet(void) { + // Get the current time in milliseconds + uint32_t currentTime = SDL_GetTicks(); + float_t delta = (currentTime - TIME_LAST) / 1000.0f; + TIME_LAST = currentTime; + return delta; +} \ No newline at end of file