diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c4c5717..f8a6872 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,6 +35,7 @@ add_subdirectory(engine) add_subdirectory(error) add_subdirectory(input) # add_subdirectory(locale) +add_subdirectory(physics) add_subdirectory(rpg) add_subdirectory(thread) add_subdirectory(time) diff --git a/src/display/camera.c b/src/display/camera.c index 3a665d0..f43cfb1 100644 --- a/src/display/camera.c +++ b/src/display/camera.c @@ -70,6 +70,23 @@ void cameraPushMatrix(camera_t *camera) { view ); break; + + case CAMERA_VIEW_TYPE_2D: + glm_mat4_identity(view); + glm_translate(view, (vec3){ + -camera->_2d.position[0], + -camera->_2d.position[1], + 0.0f + }); + glm_scale(view, (vec3){ + camera->_2d.zoom, + camera->_2d.zoom, + 1.0f + }); + break; + + default: + assertUnreachable("Invalid camera view type"); } #if DISPLAY_SDL2 diff --git a/src/display/camera.h b/src/display/camera.h index b3c86a4..0c7231d 100644 --- a/src/display/camera.h +++ b/src/display/camera.h @@ -18,7 +18,8 @@ typedef enum { typedef enum { CAMERA_VIEW_TYPE_MATRIX, - CAMERA_VIEW_TYPE_LOOKAT + CAMERA_VIEW_TYPE_LOOKAT, + CAMERA_VIEW_TYPE_2D } cameraviewtype_t; typedef struct { @@ -27,11 +28,17 @@ typedef struct { union { mat4 view; + struct { float_t position[3]; float_t target[3]; float_t up[3]; } lookat; + + struct { + float_t position[2]; + float_t zoom; + } _2d; }; union { diff --git a/src/display/scene/CMakeLists.txt b/src/display/scene/CMakeLists.txt index 0fcb97d..5d2933c 100644 --- a/src/display/scene/CMakeLists.txt +++ b/src/display/scene/CMakeLists.txt @@ -10,4 +10,5 @@ target_sources(${DUSK_TARGET_NAME} ) # Subdirs -add_subdirectory(overworld) \ No newline at end of file +add_subdirectory(overworld) +add_subdirectory(test) \ No newline at end of file diff --git a/src/display/scene/overworld/sceneoverworld.c b/src/display/scene/overworld/sceneoverworld.c index 1f746d8..ad46cf7 100644 --- a/src/display/scene/overworld/sceneoverworld.c +++ b/src/display/scene/overworld/sceneoverworld.c @@ -93,9 +93,9 @@ void sceneOverworldRenderEntity(const entity_t *entity) { // For now, just draw a placeholder quad. spriteBatchPush( &testAsset->paletteImage.texture, - entity->x, entity->y, - entity->x + TILESET_ENTITIES.tileWidth, - entity->y + TILESET_ENTITIES.tileHeight, + entity->position[0], entity->position[1], + entity->position[0] + TILESET_ENTITIES.tileWidth, + entity->position[1] + TILESET_ENTITIES.tileHeight, COLOR_WHITE, uv[0], uv[1], uv[2], uv[3] ); diff --git a/src/display/scene/scene.h b/src/display/scene/scene.h index 69c9906..7599611 100644 --- a/src/display/scene/scene.h +++ b/src/display/scene/scene.h @@ -25,6 +25,7 @@ typedef struct { typedef enum { SCENE_TYPE_LOGO, + SCENE_TYPE_TEST, SCENE_TYPE_OVERWORLD, SCENE_TYPE_COUNT diff --git a/src/display/scene/scenemanager.c b/src/display/scene/scenemanager.c index 211799d..7bf94c2 100644 --- a/src/display/scene/scenemanager.c +++ b/src/display/scene/scenemanager.c @@ -8,12 +8,20 @@ #include "scenemanager.h" #include "display/scene/overworld/sceneoverworld.h" +#include "display/scene/test/scenetest.h" scenemanager_t SCENE_MANAGER; scene_t SCENE_MANAGER_SCENES[SCENE_TYPE_COUNT] = { [SCENE_TYPE_LOGO] = { 0 }, + [SCENE_TYPE_TEST] = { + .init = sceneTestInit, + .update = sceneTestUpdate, + .render = sceneTestRender, + .dispose = sceneTestDispose + }, + [SCENE_TYPE_OVERWORLD] = { .init = sceneOverworldInit, .update = sceneOverworldUpdate, diff --git a/src/display/scene/scenemanager.h b/src/display/scene/scenemanager.h index 88e6aab..8e4c172 100644 --- a/src/display/scene/scenemanager.h +++ b/src/display/scene/scenemanager.h @@ -15,7 +15,22 @@ typedef struct { extern scenemanager_t SCENE_MANAGER; extern scene_t SCENE_MANAGER_SCENES[SCENE_TYPE_COUNT]; +/** + * Initializes the scene manager and the initial scene. + */ errorret_t sceneManagerInit(void); + +/** + * Updates all active scenes. + */ void sceneManagerUpdate(void); + +/** + * Renders all visible scenes. + */ void sceneManagerRender(void); + +/** + * Disposes of all scenes. + */ void sceneManagerDispose(void); \ No newline at end of file diff --git a/src/display/scene/test/CMakeLists.txt b/src/display/scene/test/CMakeLists.txt new file mode 100644 index 0000000..02f9e89 --- /dev/null +++ b/src/display/scene/test/CMakeLists.txt @@ -0,0 +1,10 @@ +# 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 + scenetest.c +) \ No newline at end of file diff --git a/src/display/scene/test/scenetest.c b/src/display/scene/test/scenetest.c new file mode 100644 index 0000000..e1d1354 --- /dev/null +++ b/src/display/scene/test/scenetest.c @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "scenetest.h" +#include "display/scene/scenemanager.h" +#include "display/spritebatch/spritebatch.h" + +scenetest_t SCENE_TEST; + +errorret_t sceneTestInit(void) { + cameraInit(&SCENE_TEST.camera); + SCENE_TEST.camera.projType = CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC; + SCENE_TEST.camera.nearClip = -1.0f; + SCENE_TEST.camera.farClip = 2.0f; + + SCENE_TEST.camera.viewType = CAMERA_VIEW_TYPE_2D; + SCENE_TEST.camera._2d.zoom = 2.0f; + SCENE_TEST.camera._2d.position[0] = -150.0f; + SCENE_TEST.camera._2d.position[1] = -50.0f; + + scene_t *scene = &SCENE_MANAGER_SCENES[SCENE_TYPE_TEST]; + scene->flags |= SCENE_FLAG_ACTIVE | SCENE_FLAG_VISIBLE; + + errorOk(); +} + +void sceneTestUpdate(void) { +} + +void sceneTestRender(void) { + SCENE_TEST.camera.orthographic.left = 0.0f; + SCENE_TEST.camera.orthographic.right = frameBufferGetWidth(FRAMEBUFFER_BOUND); + SCENE_TEST.camera.orthographic.top = frameBufferGetHeight(FRAMEBUFFER_BOUND); + SCENE_TEST.camera.orthographic.bottom = 0.0f; + + cameraPushMatrix(&SCENE_TEST.camera); + spriteBatchClear(); + + spriteBatchFlush(); + cameraPopMatrix(); +} + +void sceneTestDispose(void) { +} diff --git a/src/display/scene/test/scenetest.h b/src/display/scene/test/scenetest.h new file mode 100644 index 0000000..69de23d --- /dev/null +++ b/src/display/scene/test/scenetest.h @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "display/camera.h" +#include "error/error.h" + +typedef struct { + camera_t camera; +} scenetest_t; + +extern scenetest_t SCENE_TEST; + +/** + * Initialize the test scene. + */ +errorret_t sceneTestInit(void); + +/** + * Update the test scene. + */ +void sceneTestUpdate(void); + +/** + * Render the test scene. + */ +void sceneTestRender(void); + +/** + * Dispose of the test scene. + */ +void sceneTestDispose(void); \ No newline at end of file diff --git a/src/dusk.h b/src/dusk.h index 62a31d6..a794c2d 100644 --- a/src/dusk.h +++ b/src/dusk.h @@ -16,8 +16,10 @@ #include #include #include + #include #include +#include #if PSP #include diff --git a/src/error/error.c b/src/error/error.c index c6371fc..f9bab6a 100644 --- a/src/error/error.c +++ b/src/error/error.c @@ -71,7 +71,7 @@ errorret_t errorChainImpl( ) { if (retval.code == ERROR_OK) return retval; - assertNotNull(retval.state, "Error state cannot be NULL"); + assertNotNull(retval.state, "Error state NULL (Likely missing errorOk)"); assertNotNull(retval.state->message, "Message cannot be NULL"); // Create a new line string. diff --git a/src/physics/CMakeLists.txt b/src/physics/CMakeLists.txt new file mode 100644 index 0000000..c8326d0 --- /dev/null +++ b/src/physics/CMakeLists.txt @@ -0,0 +1,10 @@ +# 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 + physicscircle.c +) \ No newline at end of file diff --git a/src/physics/physics.h b/src/physics/physics.h new file mode 100644 index 0000000..601f1fa --- /dev/null +++ b/src/physics/physics.h @@ -0,0 +1,9 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "physicscircle.h" \ No newline at end of file diff --git a/src/physics/physicscircle.c b/src/physics/physicscircle.c new file mode 100644 index 0000000..8acfea6 --- /dev/null +++ b/src/physics/physicscircle.c @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "physicscircle.h" + +void physicsCircleCheckCircle( + const physicscircle_t a, + const physicscircle_t b, + physicscirclecircleresult_t *out +) { + vec2 delta; + glm_vec2_sub(b.position, a.position, delta); + float_t distSq = glm_vec2_dot(delta, delta); + float_t radiusSum = a.radius + b.radius; + float_t radiusSumSq = radiusSum * radiusSum; + + if(distSq < radiusSumSq) { + out->hit = true; + if (distSq != 0.0f) { + glm_vec2_normalize_to(delta, out->normal); + } else { + out->normal[0] = 1.0f; + out->normal[1] = 0.0f; + } + out->depth = radiusSum - sqrtf(distSq); + } else { + out->hit = false; + } +} \ No newline at end of file diff --git a/src/physics/physicscircle.h b/src/physics/physicscircle.h new file mode 100644 index 0000000..ce3f447 --- /dev/null +++ b/src/physics/physicscircle.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dusk.h" + +typedef struct physicscircle_s { + vec2 position; + float_t radius; +} physicscircle_t; + +typedef struct physicscirclecircleresult_s { + bool_t hit; + vec2 normal; + float_t depth; +} physicscirclecircleresult_t; + +/** + * Check for collision between two circles. + * + * @param a The first circle. + * @param b The second circle. + * @param out Pointer to the result structure to populate. + */ +void physicsCircleCheckCircle( + const physicscircle_t a, + const physicscircle_t b, + physicscirclecircleresult_t *out +); \ No newline at end of file diff --git a/src/rpg/entity/entity.c b/src/rpg/entity/entity.c index ab02b7b..56c8815 100644 --- a/src/rpg/entity/entity.c +++ b/src/rpg/entity/entity.c @@ -8,6 +8,7 @@ #include "entity.h" #include "assert/assert.h" #include "util/memory.h" +#include "display/tileset/tileset_entities.h" entitycallback_t ENTITY_CALLBACKS[ENTITY_TYPE_COUNT] = { { NULL, NULL }, // ENTITY_TYPE_NULL diff --git a/src/rpg/entity/entity.h b/src/rpg/entity/entity.h index 2e7c6c8..7b96dea 100644 --- a/src/rpg/entity/entity.h +++ b/src/rpg/entity/entity.h @@ -9,6 +9,7 @@ // #include "direction.h" #include "rpg/entity/player.h" #include "npc.h" +#include "physics/physics.h" typedef struct { void (*init)(entity_t *entity); @@ -25,10 +26,12 @@ typedef enum { typedef struct entity_s { // uint32_t id;// Completely unique ID for this entity. - float_t x, y; entitytype_t type; // direction_t dir; + vec2 position; + vec2 velocity; + union { player_t player; npc_t npc; diff --git a/src/rpg/entity/player.c b/src/rpg/entity/player.c index 66b0829..90a7f66 100644 --- a/src/rpg/entity/player.c +++ b/src/rpg/entity/player.c @@ -11,6 +11,7 @@ #include "input/input.h" #include "display/scene/overworld/sceneoverworld.h" +#include "display/tileset/tileset_entities.h" void playerInit(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); @@ -26,9 +27,26 @@ void playerUpdate(entity_t *entity) { inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN) }; glm_vec2_normalize(dir); - entity->x += move * dir[0]; - entity->y -= move * dir[1]; + entity->position[0] += move * dir[0]; + entity->position[1] -= move * dir[1]; - SCENE_OVERWORLD.camera.lookat.target[0] = entity->x; - SCENE_OVERWORLD.camera.lookat.target[1] = entity->y; + SCENE_OVERWORLD.camera.lookat.target[0] = entity->position[0]; + SCENE_OVERWORLD.camera.lookat.target[1] = entity->position[1]; + + // Can we move? + physicscircle_t npc = { + .position = { 32.0f, 32.0f }, + .radius = TILESET_ENTITIES.tileWidth / 2.0f + }; + + physicscircle_t self; + glm_vec2_copy(entity->position, self.position); + self.radius = npc.radius; + + physicscirclecircleresult_t result; + physicsCircleCheckCircle(self, npc, &result); + if(result.hit) { + entity->position[0] -= result.normal[0] * result.depth; + entity->position[1] -= result.normal[1] * result.depth; + } } \ No newline at end of file diff --git a/src/rpg/rpg.c b/src/rpg/rpg.c index d03d824..df75aee 100644 --- a/src/rpg/rpg.c +++ b/src/rpg/rpg.c @@ -19,8 +19,8 @@ void rpgInit() { entity_t *npc = mapEntityAdd(&testMap); entityInit(npc, ENTITY_TYPE_NPC); - npc->x = 32.0f; - npc->y = 32.0f; + npc->position[0] = 32.0f; + npc->position[1] = 32.0f; } void rpgUpdate() { diff --git a/src/rpg/world/map.h b/src/rpg/world/map.h index c316011..c01be2b 100644 --- a/src/rpg/world/map.h +++ b/src/rpg/world/map.h @@ -10,9 +10,25 @@ #define MAP_ENTITY_COUNT_MAX 32 +#define MAP_WIDTH_MAX 64 +#define MAP_HEIGHT_MAX 64 +#define MAP_TILE_COUNT_MAX (MAP_WIDTH_MAX * MAP_HEIGHT_MAX) + +typedef struct { + uint8_t id; +} tile_t; + +typedef struct { + tile_t tiles[MAP_TILE_COUNT_MAX]; +} maplayer_t; + typedef struct { entity_t entities[MAP_ENTITY_COUNT_MAX]; uint8_t entityCount; + + uint8_t width, height; + maplayer_t base; + maplayer_t overlay; } map_t; extern map_t testMap; diff --git a/src/rpg/world/tile.h b/src/rpg/world/tile.h new file mode 100644 index 0000000..566fb2d --- /dev/null +++ b/src/rpg/world/tile.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2025 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once + +typedef enum { + TILE_TYPE_NULL, +} tiletype_t; + +typedef struct { + tiletype_t type; +} tile_t; \ No newline at end of file diff --git a/src/time/time.c b/src/time/time.c index 52b4828..6e84246 100644 --- a/src/time/time.c +++ b/src/time/time.c @@ -21,6 +21,9 @@ void timeInit(void) { // Set these to something non-zero. TIME.time = TIME_STEP; TIME.delta = TIME_STEP; + + TIME.fixedDelta = TIME_STEP; + TIME.fixedTime = TIME_STEP; } void timeUpdate(void) { @@ -36,4 +39,14 @@ void timeUpdate(void) { TIME.delta = delta; assertTrue(TIME.delta >= 0.0f, "Time delta is negative"); TIME.time += TIME.delta; + + // Perform a fixed time step. + if(TIME.time - TIME.fixedTime >= TIME_STEP) { + TIME.fixedUpdate = true; + TIME.fixedDelta = TIME_STEP; + TIME.fixedTime += TIME_STEP; + } else { + TIME.fixedDelta = 0.0f; + TIME.fixedUpdate = false; + } } \ No newline at end of file diff --git a/src/time/time.h b/src/time/time.h index 63dde6a..9fd756f 100644 --- a/src/time/time.h +++ b/src/time/time.h @@ -11,6 +11,10 @@ typedef struct { float_t delta; float_t time; + + bool_t fixedUpdate; + float_t fixedDelta; + float_t fixedTime; } dusktime_t; extern dusktime_t TIME;