Switched to using tile coordinates, losing my angled movement unfortunately.
This commit is contained in:
@@ -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
|
||||
|
@@ -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)
|
@@ -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;
|
||||
|
||||
@@ -61,72 +59,117 @@ void entityUpdate(entity_t *entity) {
|
||||
|
||||
ENTITY_CALLBACKS[entity->type].update(entity);
|
||||
|
||||
if(entity->vx == 0.0f && entity->vy == 0.0f) return;
|
||||
if(entity->subX > 0) {
|
||||
entity->subX -= entity->moveSpeed;
|
||||
} else if(entity->subX < 0) {
|
||||
entity->subX += entity->moveSpeed;
|
||||
}
|
||||
|
||||
float_t newX = entity->x + entity->vx;
|
||||
float_t newY = entity->y + entity->vy;
|
||||
float_t halfTileWH = TILE_WIDTH_HEIGHT / 2.0f;
|
||||
if(entity->subY > 0) {
|
||||
entity->subY -= entity->moveSpeed;
|
||||
} else if(entity->subY < 0) {
|
||||
entity->subY += entity->moveSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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"
|
||||
);
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for collisions with other entities
|
||||
entity_t *otherEntity = ENTITIES;
|
||||
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;
|
||||
|
||||
// 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;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@@ -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;
|
||||
@@ -74,3 +79,52 @@ void entityUpdate(entity_t *entity);
|
||||
* @return The angle corresponding to the entity direction.
|
||||
*/
|
||||
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
|
||||
);
|
@@ -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;
|
||||
// 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(entity->dir != ENTITY_DIR_NORTH) {
|
||||
entityTurn(entity, ENTITY_DIR_NORTH);
|
||||
return;
|
||||
}
|
||||
entityMove(entity, moveSpeed);
|
||||
return;
|
||||
|
||||
} else if(inputIsDown(INPUT_BIND_DOWN)) {
|
||||
if(entity->dir != ENTITY_DIR_SOUTH) {
|
||||
entityTurn(entity, ENTITY_DIR_SOUTH);
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
||||
|
@@ -38,6 +38,7 @@ void gameUpdate(void) {
|
||||
// issues
|
||||
float_t timeSinceLastTick = TIME.time - TIME.lastTick;
|
||||
while(timeSinceLastTick >= DUSK_TIME_STEP) {
|
||||
|
||||
sceneUpdate();
|
||||
uiTextboxUpdate();
|
||||
eventUpdate();
|
||||
|
@@ -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
|
||||
)
|
@@ -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;
|
||||
}
|
@@ -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
|
||||
);
|
@@ -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
|
||||
|
||||
TIME.time += TIME.delta;
|
||||
assertTrue(TIME.delta >= 0.0f, "Time delta is negative");
|
||||
assertTrue(TIME.realDelta >= 0.0f, "Real time delta is negative");
|
||||
|
||||
TIME.time += TIME.delta;
|
||||
TIME.realTime += TIME.realDelta;
|
||||
}
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
);
|
||||
|
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
@@ -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;
|
||||
|
||||
char_t buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "%.1f FPS", fps);
|
||||
if(TIME.delta > 0) {
|
||||
float_t fps = 1.0f / TIME.realDelta;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@@ -8,7 +8,6 @@
|
||||
#include "time.h"
|
||||
#include "dusksdl2.h"
|
||||
|
||||
#if DUSK_TIME_DYNAMIC
|
||||
uint32_t TIME_LAST = 0;
|
||||
|
||||
float_t timeDeltaGet(void) {
|
||||
@@ -18,4 +17,3 @@
|
||||
TIME_LAST = currentTime;
|
||||
return delta;
|
||||
}
|
||||
#endif
|
Reference in New Issue
Block a user