From 349e6e7c94badeb413465d5e189f7631d3f75d67 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Fri, 10 Oct 2025 09:16:08 -0500 Subject: [PATCH] Back to floats. --- src/display/mesh/quad.c | 49 +++++++++++++++++ src/display/mesh/quad.h | 19 +++++++ src/display/spritebatch.c | 25 +++++++++ src/display/spritebatch.h | 20 +++++++ src/rpg/entity/entity.c | 29 ++++------ src/rpg/entity/entity.h | 9 ++-- src/rpg/entity/player.c | 5 +- src/rpg/entity/player.h | 3 +- src/rpg/rpg.c | 4 +- src/rpg/rpgcamera.c | 4 +- src/rpg/rpgcamera.h | 4 +- src/rpg/world/CMakeLists.txt | 1 - src/rpg/world/worldunit.c | 62 ---------------------- src/rpg/world/worldunit.h | 100 ----------------------------------- src/scene/scene/scenemap.c | 48 +++++++++-------- src/scene/scene/scenemap.h | 2 +- 16 files changed, 164 insertions(+), 220 deletions(-) delete mode 100644 src/rpg/world/worldunit.c delete mode 100644 src/rpg/world/worldunit.h diff --git a/src/display/mesh/quad.c b/src/display/mesh/quad.c index 4e2b14d..ddbfc2b 100644 --- a/src/display/mesh/quad.c +++ b/src/display/mesh/quad.c @@ -76,4 +76,53 @@ void quadBuffer( { u0, v1 }, // UV { minX, maxY, z } // Position }; +} + +void quadBuffer3D( + meshvertex_t *vertices, + const vec3 min, + const vec3 max, + const color_t color, + const vec2 uvMin, + const vec2 uvMax +) { + assertNotNull(vertices, "Vertices cannot be NULL"); + assertNotNull(min, "Min vector cannot be NULL"); + assertNotNull(max, "Max vector cannot be NULL"); + assertNotNull(uvMin, "UV Min vector cannot be NULL"); + assertNotNull(uvMax, "UV Max vector cannot be NULL"); + + // First triangle + vertices[0] = (meshvertex_t) { + { color.r, color.g, color.b, color.a }, // Color + { uvMin[0], uvMin[1] }, // UV + { min[0], min[1], min[2] } // Position + }; + vertices[1] = (meshvertex_t) { + { color.r, color.g, color.b, color.a }, // Color + { uvMax[0], uvMin[1] }, // UV + { max[0], min[1], min[2] } // Position + }; + vertices[2] = (meshvertex_t) { + { color.r, color.g, color.b, color.a }, // Color + { uvMax[0], uvMax[1] }, // UV + { max[0], max[1], min[2] } // Position + }; + + // Second triangle + vertices[3] = (meshvertex_t) { + { color.r, color.g, color.b, color.a }, // Color + { uvMin[0], uvMin[1] }, // UV + { min[0], min[1], min[2] } // Position + }; + vertices[4] = (meshvertex_t) { + { color.r, color.g, color.b, color.a }, // Color + { uvMax[0], uvMax[1] }, // UV + { max[0], max[1], min[2] } // Position + }; + vertices[5] = (meshvertex_t) { + { color.r, color.g, color.b, color.a }, // Color + { uvMin[0], uvMax[1] }, // UV + { min[0], max[1], min[2] } // Position + }; } \ No newline at end of file diff --git a/src/display/mesh/quad.h b/src/display/mesh/quad.h index b98c82e..9dcd410 100644 --- a/src/display/mesh/quad.h +++ b/src/display/mesh/quad.h @@ -45,4 +45,23 @@ void quadBuffer( const float_t v0, const float_t u1, const float_t v1 +); + +/** + * Buffers a 3D quad into the provided vertex array. + * + * @param vertices The vertex array to buffer into. + * @param min The minimum XYZ coordinates of the quad. + * @param max The maximum XYZ coordinates of the quad. + * @param color The color of the quad. + * @param uvMin The minimum UV coordinates of the quad. + * @param uvMax The maximum UV coordinates of the quad. + */ +void quadBuffer3D( + meshvertex_t *vertices, + const vec3 min, + const vec3 max, + const color_t color, + const vec2 uvMin, + const vec2 uvMax ); \ No newline at end of file diff --git a/src/display/spritebatch.c b/src/display/spritebatch.c index 46d841d..0b18999 100644 --- a/src/display/spritebatch.c +++ b/src/display/spritebatch.c @@ -55,6 +55,31 @@ void spriteBatchPush( SPRITEBATCH.spriteCount++; } +void spriteBatchPush3D( + texture_t *texture, + const vec3 min, + const vec3 max, + const color_t color, + const vec2 uv0, + const vec2 uv1 +) { + // Need to flush? + if( + SPRITEBATCH.currentTexture != texture || + SPRITEBATCH.spriteCount >= SPRITEBATCH_SPRITES_MAX + ) { + spriteBatchFlush(); + SPRITEBATCH.currentTexture = texture; + } + + quadBuffer3D( + &SPRITEBATCH.vertices[SPRITEBATCH.spriteCount * QUAD_VERTEX_COUNT], + min, max, color, uv0, uv1 + ); + + SPRITEBATCH.spriteCount++; +} + void spriteBatchClear() { SPRITEBATCH.spriteCount = 0; SPRITEBATCH.currentTexture = NULL; diff --git a/src/display/spritebatch.h b/src/display/spritebatch.h index d89af54..b2da2d9 100644 --- a/src/display/spritebatch.h +++ b/src/display/spritebatch.h @@ -61,6 +61,26 @@ void spriteBatchPush( const float_t v1 ); +/** + * Pushes a 3D sprite to the batch. This is like spriteBatchPush but takes + * 3D coordinates instead of 2D. + * + * @param texture The texture to use for the sprite. + * @param min The minimum (x,y,z) coordinate of the sprite. + * @param max The maximum (x,y,z) coordinate of the sprite. + * @param color The color to tint the sprite with. + * @param uvMin The texture coordinate for the top-left corner of the sprite. + * @param uvMax The texture coordinate for the bottom-right corner of the sprite. + */ +void spriteBatchPush3D( + texture_t *texture, + const vec3 min, + const vec3 max, + const color_t color, + const vec2 uvMin, + const vec2 uvMax +); + /** * Clears the sprite batch. This will mean calling flush renders nothing. */ diff --git a/src/rpg/entity/entity.c b/src/rpg/entity/entity.c index 0c97934..31f8452 100644 --- a/src/rpg/entity/entity.c +++ b/src/rpg/entity/entity.c @@ -51,27 +51,16 @@ void entityUpdate(entity_t *entity) { playerMovement(entity); } - // Velocity - for(uint8_t i = 0; i < WORLD_DIMENSIONS; i++) { - if(entity->velocity[i] == 0) continue; + // Add velcoity + glm_vec3_muladds(entity->velocity, TIME.delta, entity->position); - worldPosAddSubtile(&entity->position[i], entity->velocity[i]); + // Apply friction + glm_vec3_muladds( + entity->velocity, -ENTITY_FRICTION * TIME.delta, entity->velocity + ); - // Friction - worldsubtile_t v = entity->velocity[i]; - - if (v > 0) { - v -= ENTITY_FRICTION; - if (v < ENTITY_MIN_VELOCITY) v = 0; - } else if (v < 0) { - v += ENTITY_FRICTION; - if (v > -ENTITY_MIN_VELOCITY) v = 0; - } - - if ((v > 0 && v < ENTITY_FRICTION) || (v < 0 && v > -ENTITY_FRICTION)) { - v = 0; - } - - entity->velocity[i] = v; + // Clamp small velocities to zero + if(glm_vec3_norm(entity->velocity) < ENTITY_MIN_VELOCITY) { + glm_vec3_zero(entity->velocity); } } \ No newline at end of file diff --git a/src/rpg/entity/entity.h b/src/rpg/entity/entity.h index 0d7ed36..a9b2f01 100644 --- a/src/rpg/entity/entity.h +++ b/src/rpg/entity/entity.h @@ -10,10 +10,9 @@ #include "rpg/entity/player.h" #include "npc.h" #include "physics/physics.h" -#include "rpg/world/worldunit.h" -#define ENTITY_FRICTION 2 -#define ENTITY_MIN_VELOCITY 1 +#define ENTITY_FRICTION 16.0f +#define ENTITY_MIN_VELOCITY 0.1f #define ENTITY_COUNT 256 typedef struct map_s map_t; @@ -30,8 +29,8 @@ typedef struct entity_s { uint8_t id; entitytype_t type; direction_t direction; - worldpos_t position[WORLD_DIMENSIONS]; - worldsubtile_t velocity[WORLD_DIMENSIONS]; + vec3 position; + vec3 velocity; union { player_t player; diff --git a/src/rpg/entity/player.c b/src/rpg/entity/player.c index ace8c8a..8666730 100644 --- a/src/rpg/entity/player.c +++ b/src/rpg/entity/player.c @@ -11,6 +11,7 @@ #include "display/tileset/tileset_entities.h" #include "rpg/rpgcamera.h" #include "util/memory.h" +#include "time/time.h" void playerInit(entity_t *entity) { assertNotNull(entity, "Entity pointer cannot be NULL"); @@ -27,8 +28,8 @@ void playerMovement(entity_t *entity) { if(dir[0] == 0 && dir[1] == 0) return; glm_vec2_normalize(dir); - entity->velocity[0] += (worldsubtile_t)((float_t)PLAYER_SPEED * dir[0]); - entity->velocity[1] += (worldsubtile_t)((float_t)PLAYER_SPEED * dir[1]); + entity->velocity[0] += PLAYER_SPEED * dir[0] * TIME.fixedDelta; + entity->velocity[1] += PLAYER_SPEED * dir[1] * TIME.fixedDelta; // Update direction. if(dir[0] > 0) { diff --git a/src/rpg/entity/player.h b/src/rpg/entity/player.h index 41bd590..1d165d0 100644 --- a/src/rpg/entity/player.h +++ b/src/rpg/entity/player.h @@ -8,7 +8,8 @@ #pragma once #include "dusk.h" -#define PLAYER_SPEED 4 +#define PLAYER_SPEED 32.0f +#define PLAYER_SPEED_RUNNING 64.0f #define PLAYER_INTERACTION_RANGE 1.0f #define PLAYER_INTERACTION_SIZE 0.5f diff --git a/src/rpg/rpg.c b/src/rpg/rpg.c index 8f8b440..91a5971 100644 --- a/src/rpg/rpg.c +++ b/src/rpg/rpg.c @@ -21,11 +21,11 @@ errorret_t rpgInit(void) { entityInit(ent, ENTITY_TYPE_PLAYER); RPG_CAMERA.mode = RPG_CAMERA_MODE_FOLLOW_ENTITY; RPG_CAMERA.followEntity.followEntityId = ent->id; - ent->position[0].tile = 32, ent->position[1].tile = 32; + ent->position[0] = 4, ent->position[1] = 4; ent = &ENTITIES[1]; entityInit(ent, ENTITY_TYPE_NPC); - ent->position[0].tile = 40, ent->position[1].tile = 32; + ent->position[0] = 6, ent->position[1] = 6; errorOk(); } diff --git a/src/rpg/rpgcamera.c b/src/rpg/rpgcamera.c index b10c627..2d0b232 100644 --- a/src/rpg/rpgcamera.c +++ b/src/rpg/rpgcamera.c @@ -9,9 +9,7 @@ #include "util/memory.h" #include "rpg/entity/entity.h" -rpgcamera_t RPG_CAMERA = { - .position = { 0 } -}; +rpgcamera_t RPG_CAMERA; void rpgCameraInit(void) { memoryZero(&RPG_CAMERA, sizeof(rpgcamera_t)); diff --git a/src/rpg/rpgcamera.h b/src/rpg/rpgcamera.h index c76ba3b..b08fd89 100644 --- a/src/rpg/rpgcamera.h +++ b/src/rpg/rpgcamera.h @@ -6,7 +6,7 @@ */ #pragma once -#include "world/worldunit.h" +#include "dusk.h" typedef enum { RPG_CAMERA_MODE_FREE, @@ -14,7 +14,7 @@ typedef enum { } rpgcameramode_t; typedef struct { - worldpos_t position[WORLD_DIMENSIONS]; + vec3 position; rpgcameramode_t mode; union { diff --git a/src/rpg/world/CMakeLists.txt b/src/rpg/world/CMakeLists.txt index 027f8f2..15c3bec 100644 --- a/src/rpg/world/CMakeLists.txt +++ b/src/rpg/world/CMakeLists.txt @@ -6,5 +6,4 @@ # Sources target_sources(${DUSK_TARGET_NAME} PRIVATE - worldunit.c ) \ No newline at end of file diff --git a/src/rpg/world/worldunit.c b/src/rpg/world/worldunit.c deleted file mode 100644 index 6869366..0000000 --- a/src/rpg/world/worldunit.c +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#include "worldunit.h" -#include "assert/assert.h" - -void worldChunkPosAdd(worldchunkpos_t *pos, const worldsubtile_t amt) { - assertNotNull(pos, "Position pointer cannot be NULL"); - - /* - int32_t sum = (int32_t)pos->subtile + (int32_t)amt; - int32_t shifted = sum + 128; // may be negative or large - int32_t carry = div_floor(shifted, 256); // tiles to add (can be neg) - int32_t rem = mod_floor(shifted, 256); // 0..255 - int32_t new_sub = rem - 128; // back to [-128,127] - - pos->subtile = (int8_t)new_sub; - pos->tile = (uint8_t)((int32_t)pos->tile + carry); // wrap mod 256 - */ - - int32_t shiftedTotal = (int32_t)pos->subtile + (int32_t)amt + 128; - int32_t tileCarry = shiftedTotal >> 8; // divide by 256 - int32_t wrappedSubtile = shiftedTotal - (tileCarry << 8); - pos->subtile = (int8_t)(wrappedSubtile - 128); - pos->tile = (uint8_t)(pos->tile + (uint8_t)tileCarry); -} - -void worldPosAddSubtile(worldpos_t *pos, const worldsubtile_t amt) { - assertNotNull(pos, "Position pointer cannot be NULL"); - - // Same as worldChunkPosAdd but with chunk handling. - int32_t shiftedTotal = (int32_t)pos->subtile + (int32_t)amt + 128; - int32_t tileCarry = shiftedTotal >> 8; // divide by 256 - int32_t wrappedSubtile = shiftedTotal - (tileCarry << 8); - pos->subtile = (int8_t)(wrappedSubtile - 128); - int32_t newTile = (int32_t)pos->tile + (int32_t)tileCarry; - int32_t chunkCarry = newTile / WORLD_CHUNK_SIZE; - pos->tile = (uint8_t)(newTile % WORLD_CHUNK_SIZE); - pos->chunk = (uint8_t)(pos->chunk + (uint8_t)chunkCarry); -} - -float_t worldChunkPosToF32(const worldchunkpos_t pos, const uint8_t tileSize) { - const float_t scaleFactor = (float_t)tileSize * (1.0f / 256.0f); - return ( - (float_t)pos.tile * (float_t)tileSize + ((float_t)pos.subtile + 128.0f) * - scaleFactor - ); -} - -float_t worldPosToF32(const worldpos_t pos, const uint8_t tileSize) { - const float_t scaleFactor = (float_t)tileSize * (1.0f / 256.0f); - const float_t chunkFactor = WORLD_CHUNK_SIZE * (float_t)tileSize; - return ( - (float_t)pos.chunk * chunkFactor + - (float_t)pos.tile * (float_t)tileSize + - ((float_t)pos.subtile + 128.0f) * scaleFactor - ); -} \ No newline at end of file diff --git a/src/rpg/world/worldunit.h b/src/rpg/world/worldunit.h deleted file mode 100644 index 8c91a6e..0000000 --- a/src/rpg/world/worldunit.h +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2025 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dusk.h" - -#define WORLD_DIMENSIONS 3 -#define WORLD_SUBTITLE_MIN -128 -#define WORLD_SUBTITLE_MAX 127 -#define WORLD_CHUNK_SIZE 256 // Tiles per axis per chunk. - -/** - * Position in SUBTILE space in a world, each unit represents a single subtile. - * This is divided by the size of the tile, e.g. if a tile is 16x16 then there - * are 128 / tile size = units per pixel of a tile. This means there are always - * between WORLD_SUBTITLE_MIN and WORLD_SUBTITLE_MAX subtiles in a tile. - * - * The extra benefit of this is that different tile sizes don't require any game - * logic updates! - */ -typedef int8_t worldsubtile_t; - -/** - * Position in TILE space in a world, each unit represents a single tile. This - * is within CHUNK space. This is not different depending on chunk size, if the - * chunks are 32 tiles wide then the max tile value is 31. - */ -typedef uint8_t worldtile_t; - -/** - * Represents a position in a world in SUBTILE and TILE space. This is basically - * just a convenience struct so you don't have to pass two variables around. - * - * For example, an entity may be at tile (2, 3) and subtile (8, 12). - * meaning that the entity is at pixel (2 * TILE_SIZE + 8, 3 * TILE_SIZE + 12) - * in world space. - * - * This is still within CHUNK space. - */ -typedef struct worldchunkpos_s { - worldsubtile_t subtile; - worldtile_t tile; -} worldchunkpos_t; - -/** - * Position in CHUNK space in a world, each unit represents a single chunk. - */ -typedef uint8_t worldchunk_t; - -/** - * Represents a position in a world in SUBTILE, TILE and CHUNK space. This is in - * WORLD space, so this is the full position of an entity in the world. - */ -typedef struct worldpos_s { - worldsubtile_t subtile; - worldtile_t tile; - worldchunk_t chunk; -} worldpos_t; - -/** - * Adds a number of subtiles to a world chunk position, rolling over into tiles - * and chunks as necessary. - * - * @param pos Pointer to the world chunk position to modify. - * @param amt The amount of subtiles to add (can be negative). - */ -void worldChunkPosAdd(worldchunkpos_t *pos, const worldsubtile_t amt); - -/** - * Adds a number of subtiles to a world position, rolling over into tiles and - * chunks as necessary. - * - * @param pos Pointer to the world position to modify. - * @param amt The amount of subtiles to add (can be negative). - */ -void worldPosAddSubtile(worldpos_t *pos, const worldsubtile_t amt); - -/** - * Converts a world chunk position to a floating point number, given the tile - * size in pixels. - * - * @param pos Pointer to the world chunk position to convert. - * @param tileSize The size of a tile in pixels. - * @return The position as a floating point number. - */ -float_t worldChunkPosToF32(const worldchunkpos_t pos, const uint8_t tileSize); - -/** - * Converts a world position to a floating point number, given the tile size - * in pixels. - * - * @param pos Pointer to the world position to convert. - * @param tileSize The size of a tile in pixels. - * @return The position as a floating point number. - */ -float_t worldPosToF32(const worldpos_t pos, const uint8_t tileSize); \ No newline at end of file diff --git a/src/scene/scene/scenemap.c b/src/scene/scene/scenemap.c index f256f1a..d2b2af8 100644 --- a/src/scene/scene/scenemap.c +++ b/src/scene/scene/scenemap.c @@ -26,54 +26,60 @@ void sceneMapUpdate(scenedata_t *data) { } void sceneMapRender(scenedata_t *data) { + + // Look at target. + glm_vec3_scale( + RPG_CAMERA.position, + TILE_SIZE, + data->sceneMap.camera.lookat.target + ); + + // Center within tile + glm_vec3_add( + data->sceneMap.camera.lookat.target, + (vec3){TILE_SIZE / 2.0f, TILE_SIZE / 2.0f, TILE_SIZE / 2.0f }, + data->sceneMap.camera.lookat.target + ); + + // Apply pixel perfect offset and camera offset const float_t camOffset = 32.0f; const float_t pixelPerfectOffset = tanf( data->sceneMap.camera.perspective.fov / 2.0f ) * ((float_t)SCREEN.height / 2.0f); - for(uint8_t i = 0; i < WORLD_DIMENSIONS; i++) { - data->sceneMap.camera.lookat.target[i] = worldPosToF32( - RPG_CAMERA.position[i], TILE_SIZE - ); - } - glm_vec3_copy((vec3){ data->sceneMap.camera.lookat.target[0], data->sceneMap.camera.lookat.target[1] + camOffset, data->sceneMap.camera.lookat.target[2] + pixelPerfectOffset }, data->sceneMap.camera.lookat.position); - // - + // Push camera cameraPushMatrix(&data->sceneMap.camera); + // Render ents entity_t *ent = ENTITIES; do { sceneMapRenderEntity(ent); } while(++ent, ent < &ENTITIES[ENTITY_COUNT]); spriteBatchFlush(); + // Finished, pop back camera. cameraPopMatrix(); } -void sceneMapRenderEntity(const entity_t *entity) { +void sceneMapRenderEntity(entity_t *entity) { assertNotNull(entity, "Entity cannot be NULL"); if(entity->type == ENTITY_TYPE_NULL) return; - float_t x = worldPosToF32(entity->position[0], TILE_SIZE); - float_t y = worldPosToF32(entity->position[1], TILE_SIZE); - x -= TILE_SIZE * 0.5f; - y -= TILE_SIZE * 0.5f; + vec3 posMin, posMax; + glm_vec3_scale(entity->position, TILE_SIZE, posMin); + glm_vec3_add(posMin, (vec3){TILE_SIZE, TILE_SIZE, TILE_SIZE }, posMax); - spriteBatchPush( - NULL, - x, y, - x + TILE_SIZE, y + TILE_SIZE, - COLOR_RED, - 0.0f, 0.0f, - 1.0f, 1.0f - ); + vec2 uv0 = { 0.0f, 0.0f }; + vec2 uv1 = { 1.0f, 1.0f }; + + spriteBatchPush3D(NULL, posMin, posMax, COLOR_RED, uv0, uv1); } void sceneMapDispose(scenedata_t *data) { diff --git a/src/scene/scene/scenemap.h b/src/scene/scene/scenemap.h index 71f583a..83db5f9 100644 --- a/src/scene/scene/scenemap.h +++ b/src/scene/scene/scenemap.h @@ -18,7 +18,7 @@ typedef struct { errorret_t sceneMapInit(scenedata_t *data); void sceneMapUpdate(scenedata_t *data); void sceneMapRender(scenedata_t *data); -void sceneMapRenderEntity(const entity_t *entity); +void sceneMapRenderEntity(entity_t *entity); void sceneMapDispose(scenedata_t *data); static scene_t SCENE_MAP = {