diff --git a/CMakeLists.txt b/CMakeLists.txt index 3180dde..8feae46 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(blocks WIN32 src/camera.c src/main.c src/map.c + src/player.c src/rand.c src/save.c src/shader.c diff --git a/README.md b/README.md index 0bb8746..385694e 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ Tiny Minecraft clone in C and HLSL using the new SDL3 GPU API - Procedural world generation - Asynchronous chunk loading -- Blocks and sprites - Persistent worlds +- Physics - Directional shadows - Clustered dynamic lighting -- Basic transparency +- Blocks and sprites ### Building @@ -49,14 +49,17 @@ To build locally, add [SDL_shadercross](https://github.com/libsdl-org/SDL_shader ### Controls -- `WASDEQ` to move +- `WASD` to move +- `Space` to jump +- `F5` to toggle fly - `Escape` to unfocus - `Left Click` to break a block - `Middle Click` to select a block - `Right Click` to place a block - `Scroll` to change blocks - `F11` to toggle fullscreen -- `LControl` to move quickly +- `LControl` to sprint +- `EQ` to move up and down (fly only) ### Passes diff --git a/src/main.c b/src/main.c index b3092f5..784ebb8 100644 --- a/src/main.c +++ b/src/main.c @@ -4,6 +4,7 @@ #include "camera.h" #include "check.h" +#include "player.h" #include "save.h" #include "shader.h" #include "world.h" @@ -13,9 +14,6 @@ static const int PLAYER_ID = 0; static const float ATLAS_WIDTH = 512.0f; static const int ATLAS_MIP_LEVELS = 4; static const float BLOCK_WIDTH = 16.0f; -static const float PLAYER_SPEED = 0.01f; -static const float PLAYER_SENSITIVITY = 0.1f; -static const float PLAYER_REACH = 10.0f; static const int SHADOW_RESOLUTION = 4096.0f; static const float SHADOW_Y = 30.0f; static const float SHADOW_ORTHO = 300.0f; @@ -51,87 +49,10 @@ static SDL_GPUTexture* shadow_texture; static SDL_GPUSampler* linear_sampler; static SDL_GPUSampler* nearest_sampler; static camera_t shadow_camera; -static camera_t player_camera; -static world_query_t player_query; -static block_t player_block; +static player_t player; static Uint64 ticks1; static Uint64 ticks2; -static void move_player(float dt) -{ - float speed = PLAYER_SPEED; - float dx = 0.0f; - float dy = 0.0f; - float dz = 0.0f; - const bool* state = SDL_GetKeyboardState(NULL); - dx += state[SDL_SCANCODE_D]; - dx -= state[SDL_SCANCODE_A]; - dy += state[SDL_SCANCODE_E] || state[SDL_SCANCODE_SPACE]; - dy -= state[SDL_SCANCODE_Q] || state[SDL_SCANCODE_LSHIFT]; - dz += state[SDL_SCANCODE_W]; - dz -= state[SDL_SCANCODE_S]; - if (state[SDL_SCANCODE_LCTRL]) - { - speed *= 10.0f; - } - dx *= speed * dt; - dy *= speed * dt; - dz *= speed * dt; - camera_move(&player_camera, dx, dy, dz); - player_query = world_raycast(&player_camera, PLAYER_REACH); - camera_init(&shadow_camera, CAMERA_TYPE_ORTHO); - shadow_camera.ortho = SHADOW_ORTHO; - shadow_camera.far = SHADOW_FAR; - shadow_camera.x = SDL_floor(player_camera.x / CHUNK_WIDTH) * CHUNK_WIDTH; - shadow_camera.y = SHADOW_Y; - shadow_camera.z = SDL_floor(player_camera.z / CHUNK_WIDTH) * CHUNK_WIDTH; - shadow_camera.pitch = SHADOW_PITCH; - shadow_camera.yaw = SHADOW_YAW; - camera_update(&shadow_camera); -} - -static void save_or_load_player(bool save) -{ - struct - { - float x; - float y; - float z; - float pitch; - float yaw; - block_t block; - } - data; - if (save) - { - data.x = player_camera.x; - data.y = player_camera.y; - data.z = player_camera.z; - data.pitch = player_camera.pitch; - data.yaw = player_camera.yaw; - data.block = player_block; - save_set_player(PLAYER_ID, &data, sizeof(data)); - } - else - { - camera_init(&player_camera, CAMERA_TYPE_PERSPECTIVE); - player_block = BLOCK_YELLOW_TORCH; - player_camera.x = -200.0f; - player_camera.y = 50.0f; - player_camera.z = 0.0f; - if (!save_get_player(PLAYER_ID, &data, sizeof(data))) - { - return; - } - player_block = data.block; - player_camera.x = data.x; - player_camera.y = data.y; - player_camera.z = data.z; - player_camera.pitch = data.pitch; - player_camera.yaw = data.yaw; - } -} - static bool create_atlas() { char path[512] = {0}; @@ -608,9 +529,8 @@ SDL_AppResult SDLCALL SDL_AppInit(void** appstate, int argc, char** argv) set_window_icon(BLOCK_GRASS); save_init(SAVE_PATH); world_init(device); - save_or_load_player(false); - world_update(&player_camera); - move_player(0.0f); + player_save_or_load(&player, PLAYER_ID, false); + world_update(&player.camera); ticks2 = SDL_GetTicks(); ticks1 = 0; return SDL_APP_CONTINUE; @@ -620,7 +540,7 @@ void SDLCALL SDL_AppQuit(void* appstate, SDL_AppResult result) { SDL_HideWindow(window); world_free(); - save_or_load_player(true); + player_save_or_load(&player, PLAYER_ID, true); save_free(); SDL_ReleaseGPUSampler(device, linear_sampler); SDL_ReleaseGPUSampler(device, nearest_sampler); @@ -739,10 +659,23 @@ static bool resize(int width, int height) SDL_Log("Failed to create composite texture: %s", SDL_GetError()); return false; } - camera_resize(&player_camera, width, height); + camera_resize(&player.camera, width, height); return true; } +static void update_shadow_camera() +{ + camera_init(&shadow_camera, CAMERA_TYPE_ORTHO); + shadow_camera.ortho = SHADOW_ORTHO; + shadow_camera.far = SHADOW_FAR; + shadow_camera.x = SDL_floor(player.camera.x / CHUNK_WIDTH) * CHUNK_WIDTH; + shadow_camera.y = SHADOW_Y; + shadow_camera.z = SDL_floor(player.camera.z / CHUNK_WIDTH) * CHUNK_WIDTH; + shadow_camera.pitch = SHADOW_PITCH; + shadow_camera.yaw = SHADOW_YAW; + camera_update(&shadow_camera); +} + static void render_shadow(SDL_GPUCommandBuffer* cbuf) { SDL_GPUDepthStencilTargetInfo depth_info = {0}; @@ -769,8 +702,8 @@ static void render_sky(SDL_GPUCommandBuffer* cbuf, SDL_GPURenderPass* pass) { SDL_PushGPUDebugGroup(cbuf, "sky"); SDL_BindGPUGraphicsPipeline(pass, sky_pipeline); - SDL_PushGPUVertexUniformData(cbuf, 0, player_camera.proj, 64); - SDL_PushGPUVertexUniformData(cbuf, 1, player_camera.view, 64); + SDL_PushGPUVertexUniformData(cbuf, 0, player.camera.proj, 64); + SDL_PushGPUVertexUniformData(cbuf, 1, player.camera.view, 64); SDL_DrawGPUPrimitives(pass, 36, 1, 0, 0); SDL_PopGPUDebugGroup(cbuf); } @@ -783,7 +716,7 @@ static void render_opaque(SDL_GPUCommandBuffer* cbuf, SDL_GPURenderPass* pass) SDL_PushGPUDebugGroup(cbuf, "opaque"); SDL_BindGPUGraphicsPipeline(pass, opaque_pipeline); SDL_BindGPUFragmentSamplers(pass, 0, &atlas_binding, 1); - world_render(&player_camera, cbuf, pass, WORLD_FLAGS_OPAQUE | WORLD_FLAGS_LIGHT); + world_render(&player.camera, cbuf, pass, WORLD_FLAGS_OPAQUE | WORLD_FLAGS_LIGHT); SDL_PopGPUDebugGroup(cbuf); } @@ -837,8 +770,8 @@ static void render_ssao(SDL_GPUCommandBuffer* cbuf) SDL_GPUTexture* read_textures[2] = {0}; read_textures[0] = voxel_texture; read_textures[1] = position_texture; - int groups_x = (player_camera.width + 8 - 1) / 8; - int groups_y = (player_camera.height + 8 - 1) / 8; + int groups_x = (player.camera.width + 8 - 1) / 8; + int groups_y = (player.camera.height + 8 - 1) / 8; SDL_PushGPUDebugGroup(cbuf, "ssao"); SDL_BindGPUComputePipeline(compute_pass, ssao_pipeline); SDL_BindGPUComputeStorageTextures(compute_pass, 0, read_textures, 2); @@ -859,8 +792,8 @@ static void render_blur(SDL_GPUCommandBuffer* cbuf) } SDL_GPUTexture* read_textures[1]; read_textures[0] = ssao_texture; - int groups_x = (player_camera.width + 8 - 1) / 8; - int groups_y = (player_camera.height + 8 - 1) / 8; + int groups_x = (player.camera.width + 8 - 1) / 8; + int groups_y = (player.camera.height + 8 - 1) / 8; SDL_PushGPUDebugGroup(cbuf, "blur"); SDL_BindGPUComputePipeline(compute_pass, blur_pipeline); SDL_BindGPUComputeStorageTextures(compute_pass, 0, read_textures, 1); @@ -888,14 +821,14 @@ static void render_composite(SDL_GPUCommandBuffer* cbuf) read_textures[4] = position_texture; read_samplers.texture = shadow_texture; read_samplers.sampler = linear_sampler; - int groups_x = (player_camera.width + 8 - 1) / 8; - int groups_y = (player_camera.height + 8 - 1) / 8; + int groups_x = (player.camera.width + 8 - 1) / 8; + int groups_y = (player.camera.height + 8 - 1) / 8; SDL_PushGPUDebugGroup(cbuf, "composite"); SDL_BindGPUComputePipeline(compute_pass, composite_pipeline); SDL_BindGPUComputeStorageTextures(compute_pass, 0, read_textures, 5); SDL_BindGPUComputeSamplers(compute_pass, 0, &read_samplers, 1); SDL_PushGPUComputeUniformData(cbuf, 0, &shadow_camera.matrix, 64); - SDL_PushGPUComputeUniformData(cbuf, 1, player_camera.position, 12); + SDL_PushGPUComputeUniformData(cbuf, 1, player.camera.position, 12); SDL_DispatchGPUCompute(compute_pass, groups_x, groups_y, 1); SDL_EndGPUComputePass(compute_pass); SDL_PopGPUDebugGroup(cbuf); @@ -915,7 +848,7 @@ static void render_depth(SDL_GPUCommandBuffer* cbuf) } SDL_PushGPUDebugGroup(cbuf, "depth"); SDL_BindGPUGraphicsPipeline(pass, depth_pipeline); - world_render(&player_camera, cbuf, pass, WORLD_FLAGS_TRANSPARENT); + world_render(&player.camera, cbuf, pass, WORLD_FLAGS_TRANSPARENT); SDL_PopGPUDebugGroup(cbuf); SDL_EndGPURenderPass(pass); } @@ -932,22 +865,22 @@ static void render_transparent(SDL_GPUCommandBuffer* cbuf, SDL_GPURenderPass* pa SDL_PushGPUDebugGroup(cbuf, "transparent"); SDL_BindGPUGraphicsPipeline(pass, transparent_pipeline); SDL_PushGPUFragmentUniformData(cbuf, 1, &shadow_camera.matrix, 64); - SDL_PushGPUFragmentUniformData(cbuf, 2, player_camera.position, 12); + SDL_PushGPUFragmentUniformData(cbuf, 2, player.camera.position, 12); SDL_BindGPUFragmentSamplers(pass, 0, sampler_bindings, 3); - world_render(&player_camera, cbuf, pass, WORLD_FLAGS_TRANSPARENT | WORLD_FLAGS_LIGHT); + world_render(&player.camera, cbuf, pass, WORLD_FLAGS_TRANSPARENT | WORLD_FLAGS_LIGHT); SDL_PopGPUDebugGroup(cbuf); } static void render_raycast(SDL_GPUCommandBuffer* cbuf, SDL_GPURenderPass* pass) { - if (player_query.block == BLOCK_EMPTY) + if (player.query.block == BLOCK_EMPTY) { return; } SDL_PushGPUDebugGroup(cbuf, "raycast"); SDL_BindGPUGraphicsPipeline(pass, raycast_pipeline); - SDL_PushGPUVertexUniformData(cbuf, 0, player_camera.matrix, 64); - SDL_PushGPUVertexUniformData(cbuf, 1, player_query.current, 12); + SDL_PushGPUVertexUniformData(cbuf, 0, player.camera.matrix, 64); + SDL_PushGPUVertexUniformData(cbuf, 1, player.query.current, 12); SDL_DrawGPUPrimitives(pass, 36, 1, 0, 0); SDL_PopGPUDebugGroup(cbuf); } @@ -986,13 +919,13 @@ static void render_ui(SDL_GPUCommandBuffer* cbuf) SDL_GPUTextureSamplerBinding read_textures[1] = {0}; read_textures[0].texture = atlas_texture; read_textures[0].sampler = nearest_sampler; - Sint32 index = block_get_index(player_block, DIRECTION_NORTH); - int groups_x = (player_camera.width + 8 - 1) / 8; - int groups_y = (player_camera.height + 8 - 1) / 8; + Sint32 index = block_get_index(player.block, DIRECTION_NORTH); + int groups_x = (player.camera.width + 8 - 1) / 8; + int groups_y = (player.camera.height + 8 - 1) / 8; SDL_PushGPUDebugGroup(cbuf, "ui"); SDL_BindGPUComputePipeline(compute_pass, ui_pipeline); SDL_BindGPUComputeSamplers(compute_pass, 0, read_textures, 1); - SDL_PushGPUComputeUniformData(cbuf, 0, player_camera.size, 8); + SDL_PushGPUComputeUniformData(cbuf, 0, player.camera.size, 8); SDL_PushGPUComputeUniformData(cbuf, 1, &index, 4); SDL_DispatchGPUCompute(compute_pass, groups_x, groups_y, 1); SDL_EndGPUComputePass(compute_pass); @@ -1003,11 +936,11 @@ static void render_swapchain(SDL_GPUCommandBuffer* cbuf, SDL_GPUTexture* swapcha { SDL_GPUBlitInfo info = {0}; info.source.texture = composite_texture; - info.source.w = player_camera.width; - info.source.h = player_camera.height; + info.source.w = player.camera.width; + info.source.h = player.camera.height; info.destination.texture = swapchain_texture; - info.destination.w = player_camera.width; - info.destination.h = player_camera.height; + info.destination.w = player.camera.width; + info.destination.h = player.camera.height; info.load_op = SDL_GPU_LOADOP_DONT_CARE; info.filter = SDL_GPU_FILTER_NEAREST; SDL_BlitGPUTexture(cbuf, &info); @@ -1035,12 +968,12 @@ static void render() SDL_SubmitGPUCommandBuffer(cbuf); return; } - if ((width != player_camera.width || height != player_camera.height) && !resize(width, height)) + if ((width != player.camera.width || height != player.camera.height) && !resize(width, height)) { SDL_SubmitGPUCommandBuffer(cbuf); return; } - camera_update(&player_camera); + camera_update(&player.camera); render_shadow(cbuf); render_gbuffer(cbuf); render_ssao(cbuf); @@ -1060,54 +993,15 @@ SDL_AppResult SDLCALL SDL_AppIterate(void* appstate) ticks1 = ticks2; if (SDL_GetWindowRelativeMouseMode(window)) { - move_player(dt); - save_or_load_player(true); + player_move(&player, dt); + player_save_or_load(&player, PLAYER_ID, true); } - world_update(&player_camera); + update_shadow_camera(); + world_update(&player.camera); render(); return SDL_APP_CONTINUE; } -static void rotate_player(float pitch, float yaw) -{ - pitch *= -PLAYER_SENSITIVITY; - yaw *= PLAYER_SENSITIVITY; - camera_rotate(&player_camera, pitch, yaw); - player_query = world_raycast(&player_camera, PLAYER_REACH); -} - -static void break_block() -{ - if (player_query.block != BLOCK_EMPTY) - { - world_set_block(player_query.current, BLOCK_EMPTY); - } -} - -static void select_block() -{ - if (player_query.block != BLOCK_EMPTY) - { - player_block = player_query.block; - } -} - -static void place_block() -{ - if (player_query.block != BLOCK_EMPTY) - { - world_set_block(player_query.previous, player_block); - } -} - -static void change_block(int dy) -{ - static const int COUNT = BLOCK_COUNT - BLOCK_EMPTY - 1; - int block = player_block - (BLOCK_EMPTY + 1) + dy; - block = (block + COUNT) % COUNT; - player_block = block + BLOCK_EMPTY + 1; -} - SDL_AppResult SDLCALL SDL_AppEvent(void* appstate, SDL_Event* event) { switch (event->type) @@ -1117,7 +1011,7 @@ SDL_AppResult SDLCALL SDL_AppEvent(void* appstate, SDL_Event* event) case SDL_EVENT_MOUSE_MOTION: if (SDL_GetWindowRelativeMouseMode(window)) { - rotate_player(event->motion.yrel, event->motion.xrel); + player_rotate(&player, event->motion.yrel, event->motion.xrel); } break; case SDL_EVENT_KEY_DOWN: @@ -1126,6 +1020,10 @@ SDL_AppResult SDLCALL SDL_AppEvent(void* appstate, SDL_Event* event) SDL_SetWindowRelativeMouseMode(window, false); SDL_SetWindowFullscreen(window, false); } + else if (event->key.scancode == SDL_SCANCODE_F5) + { + player_toggle_controller(&player); + } else if (event->key.scancode == SDL_SCANCODE_F11) { if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN) @@ -1149,20 +1047,20 @@ SDL_AppResult SDLCALL SDL_AppEvent(void* appstate, SDL_Event* event) { if (event->button.button == SDL_BUTTON_LEFT) { - break_block(); + player_break_block(&player); } else if (event->button.button == SDL_BUTTON_MIDDLE) { - select_block(); + player_select_block(&player); } else if (event->button.button == SDL_BUTTON_RIGHT) { - place_block(); + player_place_block(&player); } } break; case SDL_EVENT_MOUSE_WHEEL: - change_block(event->wheel.y); + player_change_block(&player, event->wheel.y); break; } return SDL_APP_CONTINUE; diff --git a/src/player.c b/src/player.c new file mode 100644 index 0000000..d4ead23 --- /dev/null +++ b/src/player.c @@ -0,0 +1,283 @@ +#include + +#include "block.h" +#include "camera.h" +#include "player.h" +#include "save.h" +#include "world.h" + +typedef struct aabb +{ + float min[3]; + float max[3]; +} +aabb_t; + +static const float PHYSICS_EPSILON = 0.001f; +static const float WALK_SPEED = 5.0f; +static const float SPRINT_SPEED = 9.0f; +static const float SENSITIVITY = 0.1f; +static const float REACH = 10.0f; +static const float AIR_ACCELERATION = 6.0f; +static const float GRAVITY = 24.0f; +static const float JUMP_SPEED = 8.5f; +static const float FLY_SPEED = 0.01f; +static const float FLY_FAST_SPEED = 0.1f; +static const float COLLISION_STEP = 0.1f; +static const float GROUND_OFFSET = 0.002f; +static const float COLLISION_RADIUS = 0.3f; +static const float COLLISION_HEIGHT = 1.8f; +static const float EYE_OFFSET = 1.62f; + +static aabb_t get_aabb() +{ + return (aabb_t) {{-COLLISION_RADIUS, -EYE_OFFSET, -COLLISION_RADIUS}, + {COLLISION_RADIUS, COLLISION_HEIGHT - EYE_OFFSET, COLLISION_RADIUS}}; +} + +static bool is_solid(const float position[3]) +{ + int index[3] = {position[0], position[1], position[2]}; + return block_is_solid(world_get_block(index)); +} + +static bool is_colliding(const aabb_t *aabb, const float position[3]) +{ + int min[3]; + int max[3]; + for (int i = 0; i < 3; i++) + { + min[i] = SDL_floorf(position[i] + aabb->min[i] + PHYSICS_EPSILON); + max[i] = SDL_floorf(position[i] + aabb->max[i] - PHYSICS_EPSILON); + } + for (int bx = min[0]; bx <= max[0]; bx++) + for (int by = min[1]; by <= max[1]; by++) + for (int bz = min[2]; bz <= max[2]; bz++) + { + float location[3] = {bx, by, bz}; + if (is_solid(location)) + { + return true; + } + } + return false; +} + +static void bisect(const aabb_t* aabb, float position[3], int axis, float step) +{ + float start[3] = {position[0], position[1], position[2]}; + float lower = 0.0f; + float upper = 1.0f; + for (int i = 0; i < 8; i++) + { + float t = (lower + upper) * 0.5f; + float location[3] = {start[0], start[1], start[2]}; + location[axis] += step * t; + if (is_colliding(aabb, location)) + { + upper = t; + } + else + { + lower = t; + } + } + position[axis] = start[axis] + step * lower; +} + +static bool move(const aabb_t* aabb, float position[3], int axis, float delta) +{ + if (SDL_fabsf(delta) <= SDL_FLT_EPSILON) + { + return false; + } + int steps = SDL_ceilf(SDL_fabsf(delta) / COLLISION_STEP); + steps = SDL_max(steps, 1); + float step = delta / steps; + for (int i = 0; i < steps; i++) + { + float location[3] = {position[0], position[1], position[2]}; + location[axis] += step; + if (is_colliding(aabb, location)) + { + bisect(aabb, position, axis, step); + return true; + } + SDL_memcpy(position, location, 12); + } + return false; +} + +void player_save_or_load(player_t* player, int id, bool save) +{ + struct + { + float x; + float y; + float z; + float pitch; + float yaw; + block_t block; + } + data; + if (save) + { + data.x = player->camera.x; + data.y = player->camera.y; + data.z = player->camera.z; + data.pitch = player->camera.pitch; + data.yaw = player->camera.yaw; + data.block = player->block; + save_set_player(id, &data, sizeof(data)); + return; + } + camera_init(&player->camera, CAMERA_TYPE_PERSPECTIVE); + player->camera.x = -200.0f; + player->camera.y = 50.0f; + player->camera.z = 0.0f; + player->controller = PLAYER_CONTROLLER_WALK; + player->block = BLOCK_YELLOW_TORCH; + if (save_get_player(id, &data, sizeof(data))) + { + player->block = data.block; + player->camera.x = data.x; + player->camera.y = data.y; + player->camera.z = data.z; + player->camera.pitch = data.pitch; + player->camera.yaw = data.yaw; + } + player->query = world_raycast(&player->camera, REACH); +} + +void player_toggle_controller(player_t* player) +{ + player->controller++; + player->controller %= PLAYER_CONTROLLER_COUNT; +} + +void player_rotate(player_t* player, float pitch, float yaw) +{ + camera_rotate(&player->camera, pitch * -SENSITIVITY, yaw * SENSITIVITY); + player->query = world_raycast(&player->camera, REACH); +} + +void player_move(player_t* player, float dt) +{ + const bool* keys = SDL_GetKeyboardState(NULL); + if (player->controller == PLAYER_CONTROLLER_WALK) + { + const aabb_t aabb = get_aabb(); + dt = SDL_min(dt * 0.001f, 0.05f); + float input_x = keys[SDL_SCANCODE_D] - keys[SDL_SCANCODE_A]; + float input_z = keys[SDL_SCANCODE_W] - keys[SDL_SCANCODE_S]; + float length = SDL_sqrtf(input_x * input_x + input_z * input_z); + if (length > SDL_FLT_EPSILON) + { + input_x /= length; + input_z /= length; + } + float speed = keys[SDL_SCANCODE_LCTRL] ? SPRINT_SPEED : WALK_SPEED; + float sy = SDL_sinf(player->camera.yaw); + float cy = SDL_cosf(player->camera.yaw); + float target_x = (cy * input_x + sy * input_z) * speed; + float target_z = (sy * input_x - cy * input_z) * speed; + if (player->is_on_ground) + { + player->velocity[0] = target_x; + player->velocity[2] = target_z; + } + else + { + float blend = SDL_min(1.0f, AIR_ACCELERATION * dt); + player->velocity[0] += (target_x - player->velocity[0]) * blend; + player->velocity[2] += (target_z - player->velocity[2]) * blend; + } + if (keys[SDL_SCANCODE_SPACE] && player->is_on_ground) + { + player->velocity[1] = JUMP_SPEED; + player->is_on_ground = false; + } + if (dt <= SDL_FLT_EPSILON) + { + return; + } + player->velocity[1] -= GRAVITY * dt; + bool hits[3]; + for (int i = 0; i < 3; i++) + { + hits[i] = move(&aabb, player->camera.position, i, player->velocity[i] * dt); + } + if (hits[0]) + { + player->velocity[0] = 0.0f; + } + if (hits[2]) + { + player->velocity[2] = 0.0f; + } + if (hits[1]) + { + if (player->velocity[1] < 0.0f) + { + player->is_on_ground = true; + } + player->velocity[1] = 0.0f; + } + else + { + player->is_on_ground = false; + } + player->query = world_raycast(&player->camera, REACH); + } + else + { + float speed = keys[SDL_SCANCODE_LCTRL] ? FLY_FAST_SPEED : FLY_SPEED; + float dx = keys[SDL_SCANCODE_D] - keys[SDL_SCANCODE_A]; + float dy = (keys[SDL_SCANCODE_E] || keys[SDL_SCANCODE_SPACE]) - (keys[SDL_SCANCODE_Q] || keys[SDL_SCANCODE_LSHIFT]); + float dz = keys[SDL_SCANCODE_W] - keys[SDL_SCANCODE_S]; + camera_move(&player->camera, dx * speed * dt, dy * speed * dt, dz * speed * dt); + } +} + +void player_place_block(const player_t* player) +{ + if (player->query.block == BLOCK_EMPTY) + { + return; + } + const aabb_t aabb = get_aabb(); + for (int i = 0; i < 3; i++) + { + float min = player->camera.position[i] + aabb.min[i] + PHYSICS_EPSILON; + float max = player->camera.position[i] + aabb.max[i] - PHYSICS_EPSILON; + if (max <= player->query.previous[i] || min >= player->query.previous[i] + 1.0f) + { + world_set_block(player->query.previous, player->block); + break; + } + } +} + +void player_select_block(player_t* player) +{ + if (player->query.block != BLOCK_EMPTY) + { + player->block = player->query.block; + } +} + +void player_break_block(const player_t* player) +{ + if (player->query.block != BLOCK_EMPTY) + { + world_set_block(player->query.current, BLOCK_EMPTY); + } +} + +void player_change_block(player_t* player, int dy) +{ + static const int COUNT = BLOCK_COUNT - BLOCK_EMPTY - 1; + int block = player->block - (BLOCK_EMPTY + 1) + dy; + block = (block + COUNT) % COUNT; + player->block = block + BLOCK_EMPTY + 1; +} diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000..07ce1e1 --- /dev/null +++ b/src/player.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "block.h" +#include "camera.h" +#include "world.h" + +typedef enum player_controller +{ + PLAYER_CONTROLLER_WALK, + PLAYER_CONTROLLER_FLY, + PLAYER_CONTROLLER_COUNT, +} +player_controller_t; + +typedef struct player +{ + camera_t camera; + player_controller_t controller; + float velocity[3]; + bool is_on_ground; + world_query_t query; + block_t block; +} +player_t; + +void player_save_or_load(player_t* player, int id, bool save); +void player_toggle_controller(player_t* player); +void player_rotate(player_t* player, float pitch, float yaw); +void player_move(player_t* player, float dt); +void player_place_block(const player_t* player); +void player_select_block(player_t* player); +void player_break_block(const player_t* player); +void player_change_block(player_t* player, int dy);