// playerController.c // Player controller for Voxelthing #include"raylib.h" #include"raymath.h" #include "rlgl.h" #include"playerController.h" #include"blockTypes.h" float yaw = 0; float pitch = 0; // A basic free moving, 'noclip' style camera to get going with the most basic interactions. // returns yaw because I'm confused and am trying to help void UpdateFreeCamera(Camera3D *cam, float speed, float *yawOut, float *pitchOut) { Vector2 mouseDelta = GetMouseDelta(); yaw += mouseDelta.x * -0.002f; pitch += mouseDelta.y * -0.002f; *yawOut = yaw; *pitchOut = pitch; // Clamp pitch float clampLimit = PI / 1.80f; if (pitch > clampLimit) pitch = clampLimit; if (pitch < -clampLimit) pitch = -clampLimit; // Compute forward vector from yaw/pitch Vector3 forward = { cosf(pitch) * sinf(yaw), sinf(pitch), cosf(pitch) * cosf(yaw) }; Vector3 right = { sinf(yaw - PI / 2.0f), 0.0f, cosf(yaw - PI / 2.0f) }; // Movement input Vector3 movement = {0}; if (IsKeyDown(KEY_W)) movement = Vector3Add(movement, forward); if (IsKeyDown(KEY_S)) movement = Vector3Subtract(movement, forward); if (IsKeyDown(KEY_A)) movement = Vector3Subtract(movement, right); if (IsKeyDown(KEY_D)) movement = Vector3Add(movement, right); if (IsKeyDown(KEY_SPACE)) movement.y += 1.0f; if (IsKeyDown(KEY_LEFT_SHIFT)) movement.y -= 1.0f; // Apply movement if (Vector3Length(movement) > 0.0f) movement = Vector3Scale(Vector3Normalize(movement), speed * GetFrameTime()); cam->position = Vector3Add(cam->position, movement); // Update target so that the camera looks forward cam->target = Vector3Add(cam->position, forward); // return the value of cameraYaw... } // 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; } void DrawFaceHighlight(Vector3 blockPos, Vector3 normal) { Vector3 center = Vector3Add(blockPos, (Vector3){0.5f, 0.5f, 0.5f}); Vector3 faceCenter = Vector3Add(center, Vector3Scale(normal, 0.51f)); Vector3 u = {0}, v = {0}; if (normal.x != 0) { u = (Vector3){0, 0, 0.5f}; v = (Vector3){0, 0.5f, 0}; } else if (normal.y != 0) { u = (Vector3){0.5f, 0, 0}; v = (Vector3){0, 0, 0.5f}; } else { u = (Vector3){0.5f, 0, 0}; v = (Vector3){0, 0.5f, 0}; } Vector3 corners[4] = { Vector3Add(Vector3Add(faceCenter, u), v), Vector3Add(Vector3Subtract(faceCenter, u), v), Vector3Add(Vector3Subtract(faceCenter, u), Vector3Negate(v)), Vector3Add(Vector3Add(faceCenter, u), Vector3Negate(v)) }; // Flip winding for certain normals so the face always faces outward bool flip = false; if (normal.x == 1 || normal.y == 1 || normal.z == -1) flip = true; rlBegin(RL_QUADS); rlColor4ub(0, 255, 0, 100); if (flip) { for (int i = 3; i >= 0; i--) { rlVertex3f(corners[i].x, corners[i].y, corners[i].z); } } else { for (int i = 0; i < 4; i++) { rlVertex3f(corners[i].x, corners[i].y, corners[i].z); } } rlEnd(); }