Back to floats.
This commit is contained in:
@@ -77,3 +77,52 @@ void quadBuffer(
|
||||
{ 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
|
||||
};
|
||||
}
|
@@ -46,3 +46,22 @@ void quadBuffer(
|
||||
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
|
||||
);
|
@@ -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;
|
||||
|
@@ -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.
|
||||
*/
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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));
|
||||
|
@@ -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 {
|
||||
|
@@ -6,5 +6,4 @@
|
||||
# Sources
|
||||
target_sources(${DUSK_TARGET_NAME}
|
||||
PRIVATE
|
||||
worldunit.c
|
||||
)
|
@@ -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
|
||||
);
|
||||
}
|
@@ -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);
|
@@ -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) {
|
||||
|
@@ -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 = {
|
||||
|
Reference in New Issue
Block a user