partially done work on total source tree rework, separation of engine context and game context, generalization of renderer for different backends as well as web platform target

This commit is contained in:
2024-09-16 09:07:01 +03:00
parent ca0305feab
commit 551d60ef85
59 changed files with 2892 additions and 890 deletions

38
include/twn_audio.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef TWN_AUDIO_H
#define TWN_AUDIO_H
#include "twn_engine_api.h"
#include <stdbool.h>
typedef struct play_audio_args {
/* default: false */
bool repeat;
/* crossfade between already playing audio on a given channel, if any */
/* default: false */
bool crossfade;
/* range: 0.0f to 1.0f */
/* default: 1.0f */
float volume;
/* range: -1.0 to 1.0f */
/* default: 0.0f */
float panning;
} t_play_audio_args;
/* plays audio file at specified channel or anywhere if NULL is passed */
/* path must contain valid file extension to infer which file format it is */
/* supported formats: .ogg, .xm */
/* preserves args that are already specified on the channel */
TWN_API void play_audio(const char *path, const char *channel);
TWN_API void play_audio_ex(const char *path, const char *channel, t_play_audio_args args);
/* could be used for modifying args */
/* warn: is only valid if no other calls to audio are made */
TWN_API t_play_audio_args *get_audio_args(const char *channel);
TWN_API t_play_audio_args get_default_audio_args(void);
#endif

22
include/twn_camera.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef TWN_CAMERA_H
#define TWN_CAMERA_H
#include "twn_util.h"
#include "twn_engine_api.h"
/* TODO: make it cached? */
/* for example, perspective matrix only needs recaluclation on FOV change */
/* first person camera class */
typedef struct camera {
t_fvec3 pos; /* eye position */
t_fvec3 target; /* normalized target vector */
t_fvec3 up; /* normalized up vector */
float fov; /* field of view, in radians */
} t_camera;
TWN_API t_matrix4 camera_look_at(const t_camera *camera);
TWN_API t_matrix4 camera_perspective(const t_camera *const camera);
#endif

35
include/twn_config.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef TWN_CONFIG_H
#define TWN_CONFIG_H
/*
* this file is for configuration values which are to be set at
* compile time. generally speaking, it's for things that would be unwise to
* change mid-development without considering the work it might take to
* adapt the game logic.
*
* if you're looking for distribution-related definitions like
* APP_NAME, you should know that they are set from CMake.
*/
#define TICKS_PER_SECOND 60
#define FIXED_DELTA_TIME (1.0 / TICKS_PER_SECOND)
#define RENDER_BASE_WIDTH 640
#define RENDER_BASE_HEIGHT 360
#define RENDER_BASE_RATIO ((float)RENDER_BASE_WIDTH / (float)RENDER_BASE_HEIGHT)
#define TEXTURE_ATLAS_SIZE 2048
#define TEXTURE_ATLAS_BIT_DEPTH 32
#define TEXTURE_ATLAS_FORMAT SDL_PIXELFORMAT_RGBA32
#define NUM_KEYBIND_SLOTS 8
#define AUDIO_FREQUENCY 48000
#define AUDIO_N_CHANNELS 2
#define TEXT_FONT_TEXTURE_SIZE 1024
#define TEXT_FONT_OVERSAMPLING 4
#define TEXT_FONT_FILTERING TEXTURE_FILTER_LINEAR
#endif

38
include/twn_context.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef TWN_CONTEXT_H
#define TWN_CONTEXT_H
#include "twn_input.h"
#include "twn_engine_api.h"
#include <stdbool.h>
#include <stdint.h>
typedef struct context {
struct input_state input;
int64_t delta_time; /* preserves real time frame delta with no manipilation */
uint64_t tick_count;
/* set just once on startup */
uint64_t random_seed;
/* this should be a multiple of TICKS_PER_SECOND */
/* use it to simulate low framerate (e.g. at 60 tps, set to 2 for 30 fps) */
/* it can be changed at runtime; any resulting logic anomalies are bugs */
unsigned int update_multiplicity;
int window_w;
int window_h;
/* you may read from and write to these from game code */
void *udata;
bool debug;
bool is_running;
bool window_size_has_changed;
bool initialization_needed;
} t_ctx;
TWN_API extern t_ctx ctx;
#endif

10
include/twn_engine_api.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef TWN_ENGINE_API_H
#define TWN_ENGINE_API_H
#if defined(__WIN32)
#define TWN_API __declspec(dllexport)
#else
#define TWN_API
#endif
#endif

22
include/twn_game_api.h Normal file
View File

@ -0,0 +1,22 @@
/* include this header in game code to get the usable parts of the engine */
#ifndef GAME_API_H
#define GAME_API_H
#include "twn_context.h"
#include "twn_rendering.h"
#include "twn_audio.h"
#include "twn_util.h"
#include "twn_input.h"
#include "twn_engine_api.h"
/* sole game logic and display function.
all state must be used from and saved to supplied state pointer. */
TWN_API extern void game_tick(void);
/* called when application is closing. */
TWN_API extern void game_end(void);
#endif

101
include/twn_input.h Normal file
View File

@ -0,0 +1,101 @@
#ifndef TWN_INPUT_H
#define TWN_INPUT_H
#include "twn_config.h"
#include "twn_vec.h"
#include "twn_util.h"
#include "twn_engine_api.h"
#include <SDL2/SDL.h>
#include <stdint.h>
#include <stdbool.h>
enum button_source {
BUTTON_SOURCE_NOT_SET,
BUTTON_SOURCE_KEYBOARD_PHYSICAL,
BUTTON_SOURCE_KEYBOARD_CHARACTER,
BUTTON_SOURCE_GAMEPAD,
BUTTON_SOURCE_MOUSE,
};
union button_code {
SDL_Scancode scancode;
SDL_Keycode keycode;
SDL_GameControllerButton gamepad_button;
uint8_t mouse_button; /* SDL_BUTTON_ enum */
};
/* an input to which an action is bound */
/* it is not limited to literal buttons */
struct button {
enum button_source source;
union button_code code;
};
/* represents the collective state of a group of buttons */
/* that is, changes in the states of any of the bound buttons will affect it */
struct action {
size_t num_bindings;
/* if you bind more than NUM_KEYBIND_SLOTS (set in config.h) */
/* it forgets the first button to add the new one at the end */
struct button bindings[NUM_KEYBIND_SLOTS];
t_fvec2 position; /* set if applicable, e.g. mouse click */
bool is_pressed;
bool just_changed;
};
struct action_hash_item {
char *key;
struct action value;
};
struct input_state {
struct action_hash_item *action_hash;
const uint8_t *keyboard_state; /* array of booleans indexed by scancode */
uint32_t mouse_state; /* SDL mouse button bitmask */
t_vec2 mouse_window_position;
t_vec2 mouse_relative_position;
enum button_source last_active_source;
bool is_anything_just_pressed;
};
TWN_API void input_state_init(struct input_state *input);
TWN_API void input_state_deinit(struct input_state *input);
TWN_API void input_state_update(struct input_state *input);
TWN_API void input_bind_action_scancode(struct input_state *input,
char *action_name,
SDL_Scancode scancode);
TWN_API void input_unbind_action_scancode(struct input_state *input,
char *action_name,
SDL_Scancode scancode);
TWN_API void input_bind_action_mouse(struct input_state *input,
char *action_name,
uint8_t mouse_button);
TWN_API void input_unbind_action_mouse(struct input_state *input,
char *action_name,
uint8_t mouse_button);
TWN_API void input_add_action(struct input_state *input, char *action_name);
TWN_API void input_delete_action(struct input_state *input, char *action_name);
TWN_API bool input_is_action_pressed(struct input_state *input, char *action_name);
TWN_API bool input_is_action_just_pressed(struct input_state *input, char *action_name);
TWN_API bool input_is_action_just_released(struct input_state *input, char *action_name);
TWN_API t_fvec2 input_get_action_position(struct input_state *input, char *action_name);
TWN_API void input_set_mouse_captured(struct input_state *input, bool value);
TWN_API bool input_is_mouse_captured(struct input_state *input);
#endif

10
include/twn_loop.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef TWN_LOOP_H
#define TWN_LOOP_H
#include "twn_engine_api.h"
TWN_API int enter_loop(int argc, char **argv);
#endif

76
include/twn_rendering.h Normal file
View File

@ -0,0 +1,76 @@
#ifndef TWN_RENDERING_H
#define TWN_RENDERING_H
#include "util.h"
#include "macros/option.h"
#include "camera.h"
#include "twn_engine_api.h"
#include <SDL2/SDL.h>
#include <stdbool.h>
typedef struct push_sprite_args {
char *path;
t_frect rect;
m_option_list(
t_fvec2, texture_origin,
t_color, color,
float, rotation,
bool, flip_x,
bool, flip_y,
bool, stretch )
} t_push_sprite_args;
/* pushes a sprite onto the sprite render queue */
/* this is a simplified version of push_sprite_ex for the most common case. */
/* it assumes you want no color modulation, no rotation, no flip */
TWN_API void push_sprite(t_push_sprite_args args);
#define m_sprite(...) (push_sprite((t_push_sprite_args){__VA_ARGS__}))
/* pushes a filled rectangle onto the rectangle render queue */
TWN_API void push_rectangle(t_frect rect, t_color color);
/* pushes a filled circle onto the circle render queue */
TWN_API void push_circle(t_fvec2 position, float radius, t_color color);
TWN_API void push_text(char *string, t_fvec2 position, int height_px, t_color color, const char *font_path);
TWN_API int get_text_width(char *string, int height_px, const char *font_path);
/* pushes a textured 3d triangle onto the render queue */
/* vertices are in absolute coordinates, relative to world origin */
/* texture coordinates are in pixels */
TWN_API void unfurl_triangle(const char *path,
t_fvec3 v0,
t_fvec3 v1,
t_fvec3 v2,
t_shvec2 uv0,
t_shvec2 uv1,
t_shvec2 uv2);
// TODO: decide whether it's needed to begin with?
// intended usage for it is baked lighting, i would think.
/* pushes a colored textured 3d triangle onto the render queue */
// void unfurl_colored_triangle(const char *path,
// t_fvec3 v0,
// t_fvec3 v1,
// t_fvec3 v2,
// t_shvec2 uv0,
// t_shvec2 uv1,
// t_shvec2 uv2,
// t_color c0,
// t_color c1,
// t_color c2);
// TODO:
// http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2
// void unfurl_billboard(const char *path,
// t_fvec3 position,
// t_fvec2 scaling,
// t_frect uvs);
/* pushes a camera state to be used for all future unfurl_* commands */
TWN_API void set_camera(const t_camera *camera);
#endif

167
include/twn_util.h Normal file
View File

@ -0,0 +1,167 @@
#ifndef UTIL_H
#define UTIL_H
#include "twn_vec.h"
#include "twn_engine_api.h"
#include <SDL2/SDL.h>
#include <physfs.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <math.h>
#include <stdnoreturn.h>
/* */
/* GENERAL UTILITIES */
/* */
TWN_API void cry_impl(const char *file, const int line, const char *title, const char *text);
#define CRY(title, text) cry_impl(__FILE__, __LINE__, title, text)
#define CRY_SDL(title) cry_impl(__FILE__, __LINE__, title, SDL_GetError())
#define CRY_PHYSFS(title) \
cry_impl(__FILE__, __LINE__, title, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()))
TWN_API void log_info(const char *restrict format, ...);
TWN_API void log_critical(const char *restrict format, ...);
TWN_API void log_warn(const char *restrict format, ...);
/* for when there's absolutely no way to continue */
TWN_API noreturn void die_abruptly(void);
/* "critical" allocation functions which will log and abort() on failure. */
/* if it is reasonable to handle this gracefully, use the standard versions. */
/* the stb implementations will be configured to use these */
TWN_API void *cmalloc(size_t size);
TWN_API void *crealloc(void *ptr, size_t size);
TWN_API void *ccalloc(size_t num, size_t size);
/* DON'T FORGET ABOUT DOUBLE EVALUATION */
/* YOU THINK YOU WON'T, AND THEN YOU DO */
/* C23's typeof could fix it, so i will */
/* leave them as macros to be replaced. */
/* use the macro in tgmath.h for floats */
#define MAX SDL_max
#define MIN SDL_min
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288 /**< pi */
#endif
/* multiply by these to convert degrees <---> radians */
#define DEG2RAD (M_PI / 180)
#define RAD2DEG (180 / M_PI)
/* TODO: this is why generics were invented. sorry, i'm tired today */
TWN_API double clamp(double d, double min, double max);
TWN_API float clampf(float f, float min, float max);
TWN_API int clampi(int i, int min, int max);
/* sets buf_out to a pointer to a byte buffer which must be freed. */
/* returns the size of this buffer. */
TWN_API int64_t file_to_bytes(const char *path, unsigned char **buf_out);
/* returns a pointer to a string which must be freed */
TWN_API char *file_to_str(const char *path);
/* returns true if str ends with suffix */
TWN_API TWN_API bool strends(const char *str, const char *suffix);
/* */
/* GAME LOGIC UTILITIES */
/* */
/* 32-bit color data */
typedef struct color {
_Alignas(4)
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
} t_color;
/* a rectangle with the origin at the upper left (integer) */
typedef struct rect {
_Alignas(16)
int32_t x;
int32_t y;
int32_t w;
int32_t h;
} t_rect;
/* a rectangle with the origin at the upper left (floating point) */
typedef struct frect {
_Alignas(16)
float x;
float y;
float w;
float h;
} t_frect;
TWN_API bool intersect_rect(const t_rect *a, const t_rect *b, t_rect *result);
TWN_API bool intersect_frect(const t_frect *a, const t_frect *b, t_frect *result);
/* TODO: generics and specials (see m_to_fvec2() for an example)*/
TWN_API t_frect to_frect(t_rect rect);
TWN_API t_fvec2 frect_center(t_frect rect);
typedef struct matrix4 {
t_fvec4 row[4];
} t_matrix4;
/* decrements an lvalue (which should be an int), stopping at 0 */
/* meant for tick-based timers in game logic */
/*
* example:
* tick_timer(&player->jump_air_timer);
*/
TWN_API void tick_timer(int *value);
/* decrements a floating point second-based timer, stopping at 0.0 */
/* meant for poll based real time logic in game logic */
/* note that it should be decremented only on the next tick after its creation */
TWN_API void tick_ftimer(float *value);
/* same as `tick_ftimer` but instead of clamping it repeats */
/* returns true if value was cycled */
TWN_API bool repeat_ftimer(float *value, float at);
/* http://www.azillionmonkeys.com/qed/sqroot.html */
static inline float fast_sqrt(float x)
{
union {
float f;
uint32_t u;
} pun = {.f = x};
pun.u += 127 << 23;
pun.u >>= 1;
return pun.f;
}
static inline t_fvec2 fast_cossine(float a) {
const float s = sinf(a);
return (t_fvec2){
.x = fast_sqrt(1.0f - s * s) * (a >= (float)M_PI_2 && a < (float)(M_PI + M_PI_2) ? -1 : 1),
.y = s
};
}
#endif

164
include/twn_vec.h Normal file
View File

@ -0,0 +1,164 @@
#ifndef TWN_VEC_H
#define TWN_VEC_H
#include <stdint.h>
#include <math.h>
/* a point in some space (integer) */
typedef struct vec2 {
_Alignas(8)
int32_t x;
int32_t y;
} t_vec2;
/* a point in some space (floating point) */
typedef struct fvec2 {
_Alignas(8)
float x;
float y;
} t_fvec2;
/* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */
typedef struct fvec3 {
_Alignas(16)
float x;
float y;
float z;
} t_fvec3;
/* a point in some three dimension space (floating point) */
/* y goes up, x goes to the right */
typedef struct fvec4 {
_Alignas(16)
float x;
float y;
float z;
float w;
} t_fvec4;
/* a point in some space (short) */
typedef struct shvec2 {
_Alignas(4)
int16_t x;
int16_t y;
} t_shvec2;
/* aren't macros to prevent double evaluation with side effects */
/* maybe could be inlined? i hope LTO will resolve this */
static inline t_fvec2 fvec2_from_vec2(t_vec2 vec) {
return (t_fvec2) {
.x = (float)vec.x,
.y = (float)vec.y,
};
}
static inline t_fvec2 fvec2_from_shvec2(t_shvec2 vec) {
return (t_fvec2) {
.x = (float)vec.x,
.y = (float)vec.y,
};
}
static inline t_fvec3 fvec3_add(t_fvec3 a, t_fvec3 b) {
return (t_fvec3) { a.x + b.x, a.y + b.y, a.z + b.z };
}
static inline t_fvec3 fvec3_sub(t_fvec3 a, t_fvec3 b) {
return (t_fvec3) { a.x - b.x, a.y - b.y, a.z - b.z };
}
static inline t_fvec2 fvec2_div(t_fvec2 a, t_fvec2 b) {
return (t_fvec2) { a.x / b.x, a.y / b.y };
}
static inline t_fvec2 fvec2_scale(t_fvec2 a, float s) {
return (t_fvec2) { a.x * s, a.y * s };
}
static inline t_fvec3 fvec3_scale(t_fvec3 a, float s) {
return (t_fvec3) { a.x * s, a.y * s, a.z * s };
}
static inline float fvec3_dot(t_fvec3 a, t_fvec3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static inline t_fvec3 fvec3_cross(t_fvec3 a, t_fvec3 b) {
return (t_fvec3) {
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x,
};
}
/* TODO: fast_sqrt version? */
static inline t_fvec3 fvec3_norm(t_fvec3 a) {
const float n = sqrtf(fvec3_dot(a, a));
/* TODO: do we need truncating over epsilon as cglm does? */
return fvec3_scale(a, 1.0f / n);
}
static inline t_fvec3 fvec3_rotate(t_fvec3 v, float angle, t_fvec3 axis) {
/* from cglm */
t_fvec3 v1, v2, k;
float c, s;
c = cosf(angle);
s = sinf(angle);
k = fvec3_norm(axis);
/* Right Hand, Rodrigues' rotation formula:
v = v*cos(t) + (kxv)sin(t) + k*(k.v)(1 - cos(t))
*/
v1 = fvec3_scale(v, c);
v2 = fvec3_cross(k, v);
v2 = fvec3_scale(v2, s);
v1 = fvec3_add(v1, v2);
v2 = fvec3_scale(k, fvec3_dot(k, v) * (1.0f - c));
v = fvec3_add(v1, v2);
return v;
}
#define m_to_fvec2(p_any_vec2) (_Generic((p_any_vec2), \
t_vec2: fvec2_from_vec2, \
t_shvec2: fvec2_from_shvec2 \
)(p_any_vec2))
#define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
t_fvec3: fvec3_sub \
)(p_any_vec0, p_any_vec1))
#define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
t_fvec2: fvec2_div \
)(p_any_vec0, p_any_vec1))
#define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec), \
t_fvec2: fvec2_scale, \
t_fvec3: fvec3_scale \
)(p_any_vec, p_any_scalar))
#define m_vec_dot(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
t_fvec3: fvec3_dot \
)(p_any_vec0, p_any_vec1))
#define m_vec_cross(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \
t_fvec3: fvec3_cross \
)(p_any_vec0, p_any_vec1))
#define m_vec_norm(p_any_vec) (_Generic((p_any_vec), \
t_fvec3: fvec3_norm \
)(p_any_vec))
#endif