Switched to using tile coordinates, losing my angled movement unfortunately.

This commit is contained in:
2025-08-18 21:40:01 -05:00
parent bcb1616201
commit d79e12ffaa
18 changed files with 275 additions and 647 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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);
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
);

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -38,6 +38,7 @@ void gameUpdate(void) {
// issues
float_t timeSinceLastTick = TIME.time - TIME.lastTick;
while(timeSinceLastTick >= DUSK_TIME_STEP) {
sceneUpdate();
uiTextboxUpdate();
eventUpdate();

View File

@@ -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
)

View File

@@ -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;
}

View File

@@ -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
);

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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
);

View File

@@ -1,8 +0,0 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once

View File

@@ -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);
}

View File

@@ -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
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;
}