// playerController.c #include "raylib.h" #include "raymath.h" #include "playerController.h" #include "blockTypes.h" #include void UpdatePlayer(Player *player) { Vector2 mouseDelta = GetMouseDelta(); player->playerOrientation.x += mouseDelta.x * -0.002f; player->playerOrientation.y += mouseDelta.y * -0.002f; float limit = PI / 1.8f; if (player->playerOrientation.y > limit) player->playerOrientation.y = limit; if (player->playerOrientation.y < -limit) player->playerOrientation.y = -limit; float yaw = player->playerOrientation.x; float pitch = player->playerOrientation.y; player->forward = (Vector3){ cosf(pitch) * sinf(yaw), sinf(pitch), cosf(pitch) * cosf(yaw) }; player->right = (Vector3){ sinf(yaw - PI/2.0f), 0.0f, cosf(yaw - PI/2.0f) }; Vector3 movement = {0}; if (IsKeyDown(KEY_W)) movement = Vector3Add(movement, player->forward); if (IsKeyDown(KEY_S)) movement = Vector3Subtract(movement, player->forward); if (IsKeyDown(KEY_A)) movement = Vector3Subtract(movement, player->right); if (IsKeyDown(KEY_D)) movement = Vector3Add(movement, player->right); if (IsKeyDown(KEY_SPACE)) movement.y += 1.0f; if (IsKeyDown(KEY_LEFT_SHIFT)) movement.y -= 1.0f; if (Vector3Length(movement) > 0.0f) movement = Vector3Scale(Vector3Normalize(movement), player->moveSpeed * GetFrameTime()); player->mapPosition = Vector3Add(player->mapPosition, movement); player->camera.position = player->mapPosition; player->camera.target = Vector3Add(player->mapPosition, player->forward); } // An implementation of DDA (digital differential analyzer), steps through each voxel boundary along a ray cast from origin along direction to maxDistance RaycastHit RaycastChunk(const Chunk *chunk, Vector3 origin, Vector3 direction, float maxDistance) { RaycastHit result = {0}; // Initialize result: no hit, zeroed values direction = Vector3Normalize(direction); // Ensure direction is a unit vector // Nudge the origin slightly to avoid precision issues at block boundaries origin = Vector3Add(origin, Vector3Scale(direction, 0.001f)); // Determine the next voxel boundary to cross along each axis float nextX = (direction.x >= 0) ? ceilf(origin.x) : floorf(origin.x); float nextY = (direction.y >= 0) ? ceilf(origin.y) : floorf(origin.y); float nextZ = (direction.z >= 0) ? ceilf(origin.z) : floorf(origin.z); // Get integer voxel coordinates for current position int x = (int)floorf(origin.x); int y = (int)floorf(origin.y); int z = (int)floorf(origin.z); // Determine step direction: +1 or -1 along each axis int stepX = (direction.x >= 0) ? 1 : -1; int stepY = (direction.y >= 0) ? 1 : -1; int stepZ = (direction.z >= 0) ? 1 : -1; // How far to travel along the ray to cross a voxel in each axis float tDeltaX = (direction.x == 0) ? INFINITY : fabsf(1.0f / direction.x); float tDeltaY = (direction.y == 0) ? INFINITY : fabsf(1.0f / direction.y); float tDeltaZ = (direction.z == 0) ? INFINITY : fabsf(1.0f / direction.z); // Distance from origin to the first voxel boundary (for each axis) float tMaxX = (direction.x == 0) ? INFINITY : (nextX - origin.x) / direction.x; float tMaxY = (direction.y == 0) ? INFINITY : (nextY - origin.y) / direction.y; float tMaxZ = (direction.z == 0) ? INFINITY : (nextZ - origin.z) / direction.z; float t = 0.0f; // Total traveled distance along the ray Vector3 lastNormal = {0}; // Which face was entered (used for highlighting/interactions) // Walk the ray through the voxel grid until we exceed maxDistance while (t < maxDistance) { // Check if the current voxel is inside the chunk bounds if (x >= 0 && y >= 0 && z >= 0 && x < CHUNK_SIZE_X && y < CHUNK_SIZE_Y && z < CHUNK_SIZE_Z) { int blockID = chunk->blocks[x][y][z].type; // If it's not air, we hit something! if (blockID != BLOCK_AIR) { result.hit = true; result.blockID = blockID; result.position = (Vector3){x, y, z}; result.normal = lastNormal; result.t = t; return result; } } // Move to the next voxel along the smallest tMax (i.e., the closest boundary) if (tMaxX < tMaxY && tMaxX < tMaxZ) { x += stepX; t = tMaxX; tMaxX += tDeltaX; lastNormal = (Vector3){-stepX, 0, 0}; // Normal points opposite the ray step } else if (tMaxY < tMaxZ) { y += stepY; t = tMaxY; tMaxY += tDeltaY; lastNormal = (Vector3){0, -stepY, 0}; } else { z += stepZ; t = tMaxZ; tMaxZ += tDeltaZ; lastNormal = (Vector3){0, 0, -stepZ}; } } // If no block was hit, return default (no hit) return result; } RaycastHit GetPlayerRaycastHit(Player *player, Chunk *chunk, float maxDistance) { Ray ray = { .position = player->mapPosition, .direction = Vector3Normalize(player->forward) }; return RaycastChunk(chunk, ray.position, ray.direction, maxDistance); } void HandleBlockInteraction(Player *player, Chunk *chunk, RaycastHit hit, int blockSelection) { if (hit.hit && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { chunk->blocks[(int)hit.position.x][(int)hit.position.y][(int)hit.position.z].type = BLOCK_AIR; } if (hit.hit && IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { int px = (int)(hit.position.x + hit.normal.x); int py = (int)(hit.position.y + hit.normal.y); int pz = (int)(hit.position.z + hit.normal.z); if (px >= 0 && px < CHUNK_SIZE_X && py >= 0 && py < CHUNK_SIZE_Y && pz >= 0 && pz < CHUNK_SIZE_Z) { chunk->blocks[px][py][pz].type = blockSelection; } } }