Better player encapsulation. World persistence. Cleaned up CPU and GPU memory leaks.

This commit is contained in:
Jake 2025-05-30 10:56:22 -04:00
parent 585f72a4bd
commit 701c72a2a5
10 changed files with 168 additions and 228 deletions

View File

@ -1,12 +1,15 @@
# voxelThing # voxelThing
Jake's Raylib Minecraft Clone Jake's Raylib Minecraft Clone
Implements a voxel chunk renderer. Chunks are stored as a 3D array of block structs elements which define block types and later other block data. The chunk is converted to a mesh such that only faces exposed to air blocks or the chunk boarders are rendered. Implements a voxel chunk renderer. Chunks are stored as a 3D array of block struct elements which define block types and later other block data. The chunk is converted to a mesh such that only faces exposed to air blocks or the chunk boarders are rendered.
Code is rough and messy and not much is implemented yet. Code is rough and messy and not much is implemented yet.
Once a chunk has been generated, its state is persistent across game restarts.
Controls: Controls:
- WASD - move - WASD - move
- Left Shift - Down, Space - Up
- Mouse - look - Mouse - look
- LMB - Remove block - LMB - Remove block
- RMB - Place Block - RMB - Place Block
@ -20,6 +23,6 @@ Depends on Raylib.
1) Install Raylib. 1) Install Raylib.
2) Clone the repo. 2) Clone the repo.
3) Build by running make. 3) CD into the repo and build by running make.
4) run bin/voxelThing 4) run bin/voxelThing

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -14,6 +14,12 @@ typedef struct {
int type; // 0 = air, 1 = dirt, etc. int type; // 0 = air, 1 = dirt, etc.
} Block; } Block;
typedef struct {
Block blocks[CHUNK_SIZE_X][CHUNK_SIZE_Y][CHUNK_SIZE_Z];
Mesh mesh; // Owned by the chunk, valid only at runtime
bool hasMesh; //
} Chunk;
// 6 directions for checking neighbors: +/-X, +/-Y, +/-Z // 6 directions for checking neighbors: +/-X, +/-Y, +/-Z
static const int faceOffsets[6][3] = { static const int faceOffsets[6][3] = {
{ -1, 0, 0 }, // left { -1, 0, 0 }, // left
@ -24,13 +30,16 @@ static const int faceOffsets[6][3] = {
{ 0, 0, 1 } // front { 0, 0, 1 } // front
}; };
typedef struct {
Block blocks[CHUNK_SIZE_X][CHUNK_SIZE_Y][CHUNK_SIZE_Z];
} Chunk;
// Function to check if a face of a block is exposed. // Function to check if a face of a block is exposed.
int IsBlockFaceExposed(Chunk *chunk, int x, int y, int z, int dir); int IsBlockFaceExposed(Chunk *chunk, int x, int y, int z, int dir);
// Function that places a tree dumbly.
void PlaceTreeAt(Chunk *chunk, int x, int y, int z) ; void PlaceTreeAt(Chunk *chunk, int x, int y, int z) ;
// Save chunk to disk.
bool SaveChunk(const Chunk *chunk, const char *filename);
// Load chunk from disk.
bool LoadChunk(Chunk *chunk, const char *filename);
#endif #endif

View File

@ -5,6 +5,17 @@
#include "raylib.h" #include "raylib.h"
#include "chunkStructures.h" #include "chunkStructures.h"
typedef struct {
Vector3 mapPosition; // Player's world position (camera position)
Vector2 playerOrientation; // Yaw (x) and pitch (y), in radians
Vector3 forward; // Direction vector computed from yaw/pitch
Vector3 right; // Right vector
float moveSpeed; // Movement speed
Camera3D camera;
} Player;
void UpdatePlayer(Player *player);
typedef struct { typedef struct {
bool hit; bool hit;
Vector3 position; Vector3 position;
@ -15,8 +26,12 @@ typedef struct {
RaycastHit RaycastChunk(const Chunk *chunk, Vector3 origin, Vector3 direction, float maxDistance); RaycastHit RaycastChunk(const Chunk *chunk, Vector3 origin, Vector3 direction, float maxDistance);
RaycastHit GetPlayerRaycastHit(Player *player, Chunk *chunk, float maxDistance);
void UpdateFreeCamera(Camera3D *cam, float speed, float *yawOut, float *pitchOut); void UpdateFreeCamera(Camera3D *cam, float speed, float *yawOut, float *pitchOut);
void DrawFaceHighlight(Vector3 blockPos, Vector3 normal); void DrawFaceHighlight(Vector3 blockPos, Vector3 normal);
void HandleBlockInteraction(Player *player, Chunk *chunk, RaycastHit hit, int blockSelection);
#endif #endif

0
saves/.gitkeep Normal file
View File

View File

@ -10,11 +10,11 @@ void GenerateFlatChunk(Chunk *chunk) {
for (int x = 0; x < CHUNK_SIZE_X; x++) { for (int x = 0; x < CHUNK_SIZE_X; x++) {
for (int z = 0; z < CHUNK_SIZE_Z; z++) { for (int z = 0; z < CHUNK_SIZE_Z; z++) {
for (int y = 0; y < CHUNK_SIZE_Y; y++) { for (int y = 0; y < CHUNK_SIZE_Y; y++) {
if (y < 3) { if (y < 59) {
chunk->blocks[x][y][z].type = BLOCK_STONE; chunk->blocks[x][y][z].type = BLOCK_STONE;
} else if (y < 7) { } else if (y < 64) {
chunk->blocks[x][y][z].type = BLOCK_DIRT; chunk->blocks[x][y][z].type = BLOCK_DIRT;
} else if (y == 7) { } else if (y == 64) {
chunk->blocks[x][y][z].type = BLOCK_GRASS; chunk->blocks[x][y][z].type = BLOCK_GRASS;
} else { } else {
chunk->blocks[x][y][z].type = BLOCK_AIR; chunk->blocks[x][y][z].type = BLOCK_AIR;

View File

@ -1,8 +1,5 @@
// chunkRenderer.c // chunkRenderer.c
// Rendering and meshing functions for voxelThing // Rendering and meshing functions for voxelThing
// TODO: Memory is allocated dynamically in here but never freed.
// TODO: Chunk meshes need to be unloaded from the VRAM with UnloadMesh() when chunks are updated.
// For now new meshes are just added to the VRAM after each update...
#include <stdlib.h> #include <stdlib.h>
#include "raylib.h" #include "raylib.h"
@ -14,8 +11,8 @@
#define ATLAS_SIZE 256 #define ATLAS_SIZE 256
#define ATLAS_TILES_PER_ROW (ATLAS_SIZE / TILE_SIZE) #define ATLAS_TILES_PER_ROW (ATLAS_SIZE / TILE_SIZE)
/// Returns the UV coordinate for a given tile index and corner index (03), // Returns the UV coordinate for a given tile index and corner index (03),
/// assuming tiles are arranged in a grid in the texture atlas. // assuming tiles are arranged in a grid in the texture atlas.
Vector2 GetTileUV(int tile, int corner) { Vector2 GetTileUV(int tile, int corner) {
int tileX = tile % ATLAS_TILES_PER_ROW; int tileX = tile % ATLAS_TILES_PER_ROW;
int tileY = tile / ATLAS_TILES_PER_ROW; int tileY = tile / ATLAS_TILES_PER_ROW;
@ -33,7 +30,7 @@ Vector2 GetTileUV(int tile, int corner) {
} }
} }
/// Generates a mesh for the given chunk by stitching together visible block faces. // Generates a mesh for the given chunk by stitching together visible block faces.
Mesh GenerateChunkMesh(Chunk *chunk) { Mesh GenerateChunkMesh(Chunk *chunk) {
const int maxFaces = CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z * 6; const int maxFaces = CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z * 6;
const int maxVerts = maxFaces * 4; // 4 verts per face const int maxVerts = maxFaces * 4; // 4 verts per face
@ -141,7 +138,7 @@ Mesh GenerateChunkMesh(Chunk *chunk) {
mesh.normals = (float *)normals; mesh.normals = (float *)normals;
mesh.indices = indices; mesh.indices = indices;
UploadMesh(&mesh, false); UploadMesh(&mesh, true); // True here tells the function to free the CPU side allocated memory.
return mesh; return mesh;
} }

View File

@ -8,6 +8,23 @@
#include "rlgl.h" #include "rlgl.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdio.h>
bool SaveChunk(const Chunk *chunk, const char *filename) {
FILE *fp = fopen(filename, "wb");
if (!fp) return false;
size_t written = fwrite(chunk->blocks, sizeof(Block), CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z, fp);
fclose(fp);
return written == CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z;
}
bool LoadChunk(Chunk *chunk, const char *filename) {
FILE *fp = fopen(filename, "rb");
if (!fp) return false;
size_t read = fread(chunk->blocks, sizeof(Block), CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z, fp);
fclose(fp);
return read == CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z;
}
// Places a tree at the position specified by x, y, z // Places a tree at the position specified by x, y, z
void PlaceTreeAt(Chunk *chunk, int x, int y, int z) { void PlaceTreeAt(Chunk *chunk, int x, int y, int z) {

View File

@ -1,60 +1,48 @@
// playerController.c // playerController.c
// Player controller for Voxelthing
#include "raylib.h" #include "raylib.h"
#include "raymath.h" #include "raymath.h"
#include "rlgl.h"
#include "playerController.h" #include "playerController.h"
#include "blockTypes.h" #include "blockTypes.h"
#include <math.h>
float yaw = 0; void UpdatePlayer(Player *player) {
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(); Vector2 mouseDelta = GetMouseDelta();
player->playerOrientation.x += mouseDelta.x * -0.002f;
player->playerOrientation.y += mouseDelta.y * -0.002f;
yaw += mouseDelta.x * -0.002f; float limit = PI / 1.8f;
pitch += mouseDelta.y * -0.002f; if (player->playerOrientation.y > limit) player->playerOrientation.y = limit;
*yawOut = yaw; if (player->playerOrientation.y < -limit) player->playerOrientation.y = -limit;
*pitchOut = pitch;
// Clamp pitch float yaw = player->playerOrientation.x;
float clampLimit = PI / 1.80f; float pitch = player->playerOrientation.y;
if (pitch > clampLimit) pitch = clampLimit;
if (pitch < -clampLimit) pitch = -clampLimit;
// Compute forward vector from yaw/pitch player->forward = (Vector3){
Vector3 forward = {
cosf(pitch) * sinf(yaw), cosf(pitch) * sinf(yaw),
sinf(pitch), sinf(pitch),
cosf(pitch) * cosf(yaw) cosf(pitch) * cosf(yaw)
}; };
Vector3 right = { player->right = (Vector3){
sinf(yaw - PI/2.0f), sinf(yaw - PI/2.0f),
0.0f, 0.0f,
cosf(yaw - PI/2.0f) cosf(yaw - PI/2.0f)
}; };
// Movement input
Vector3 movement = {0}; Vector3 movement = {0};
if (IsKeyDown(KEY_W)) movement = Vector3Add(movement, forward); if (IsKeyDown(KEY_W)) movement = Vector3Add(movement, player->forward);
if (IsKeyDown(KEY_S)) movement = Vector3Subtract(movement, forward); if (IsKeyDown(KEY_S)) movement = Vector3Subtract(movement, player->forward);
if (IsKeyDown(KEY_A)) movement = Vector3Subtract(movement, right); if (IsKeyDown(KEY_A)) movement = Vector3Subtract(movement, player->right);
if (IsKeyDown(KEY_D)) movement = Vector3Add(movement, right); if (IsKeyDown(KEY_D)) movement = Vector3Add(movement, player->right);
if (IsKeyDown(KEY_SPACE)) movement.y += 1.0f; if (IsKeyDown(KEY_SPACE)) movement.y += 1.0f;
if (IsKeyDown(KEY_LEFT_SHIFT)) movement.y -= 1.0f; if (IsKeyDown(KEY_LEFT_SHIFT)) movement.y -= 1.0f;
// Apply movement
if (Vector3Length(movement) > 0.0f) if (Vector3Length(movement) > 0.0f)
movement = Vector3Scale(Vector3Normalize(movement), speed * GetFrameTime()); movement = Vector3Scale(Vector3Normalize(movement), player->moveSpeed * GetFrameTime());
cam->position = Vector3Add(cam->position, movement);
// Update target so that the camera looks forward player->mapPosition = Vector3Add(player->mapPosition, movement);
cam->target = Vector3Add(cam->position, forward); player->camera.position = player->mapPosition;
// return the value of cameraYaw... 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 // An implementation of DDA (digital differential analyzer), steps through each voxel boundary along a ray cast from origin along direction to maxDistance
@ -136,47 +124,29 @@ RaycastHit RaycastChunk(const Chunk *chunk, Vector3 origin, Vector3 direction, f
return result; 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}; RaycastHit GetPlayerRaycastHit(Player *player, Chunk *chunk, float maxDistance) {
Ray ray = {
if (normal.x != 0) { .position = player->mapPosition,
u = (Vector3){0, 0, 0.5f}; .direction = Vector3Normalize(player->forward)
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))
}; };
return RaycastChunk(chunk, ray.position, ray.direction, maxDistance);
// 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(); 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;
}
}
}

View File

@ -1,57 +1,31 @@
// voxelThing.c // voxelThing.c (cleaned-up main loop)
// A voxel chunk rendering doodad written in C with Raylib
// External libraries
#include "raylib.h" #include "raylib.h"
#include "raymath.h" #include "raymath.h"
#include "rlgl.h" #include "rlgl.h"
#include "stdio.h" #include "stdio.h"
// Project files
#include "chunkStructures.h" #include "chunkStructures.h"
#include "chunkRenderer.h" #include "chunkRenderer.h"
#include "blockTypes.h" #include "blockTypes.h"
#include "playerController.h" #include "playerController.h"
#include "chunkGenerator.h" #include "chunkGenerator.h"
// Bunch of global variables to clean up later.
float cameraYaw = 0;
float cameraPitch = 0;
bool paused = false;
RaycastHit hit;
int blockSelection = BLOCK_SAND;
// Random helper function for returning what direction you're facing.'
const char* GetCompassDirection(float yaw) {
yaw = fmodf(yaw * RAD2DEG + 360.0f, 360.0f);
if (yaw < 22.5f || yaw >= 337.5f) return "South (+Z)";
if (yaw < 67.5f) return "Southwest (+X+Z)";
if (yaw < 112.5f) return "West (+X)";
if (yaw < 157.5f) return "Northwest (+X-Z)";
if (yaw < 202.5f) return "North (-Z)";
if (yaw < 247.5f) return "Northeast (-X-Z)";
if (yaw < 292.5f) return "East (-X)";
return "Southeast (-X+Z)";
}
// Another random helper function, this time for checking if a Vector3 is non-zero.
static inline bool Vector3IsNonZero(Vector3 v) {
return v.x != 0.0f || v.y != 0.0f || v.z != 0.0f;
}
int main(void) { int main(void) {
// --- Screen setup --- // --- Screen setup ---
int screenWidth = 800;
int screenHeight = 600;
SetConfigFlags(FLAG_WINDOW_RESIZABLE); SetConfigFlags(FLAG_WINDOW_RESIZABLE);
InitWindow(screenWidth, screenHeight, "VoxelThing"); InitWindow(800, 600, "VoxelThing");
SetExitKey(-1); SetExitKey(-1);
DisableCursor(); // Lock mouse to window for FPS-style camera DisableCursor();
// --- World generation --- // --- World generation ---
Chunk chunk; Chunk chunk;
if (!LoadChunk(&chunk, "saves/chunk_0_0_0.dat")) {
// There was no save, gotta generate a fresh chunk to play with.
printf("--- WORLDGEN--- No save, creating new chunk.\n");
GenerateFlatChunk(&chunk); GenerateFlatChunk(&chunk);
PlaceTreeAt(&chunk, 8, 7, 8); PlaceTreeAt(&chunk, 8, 64, 8);
SaveChunk(&chunk, "saves/chunk_0_0_0.dat");
}
Mesh chunkMesh = GenerateChunkMesh(&chunk); Mesh chunkMesh = GenerateChunkMesh(&chunk);
// --- Load textures and materials --- // --- Load textures and materials ---
@ -59,39 +33,40 @@ int main(void) {
Material mat = LoadMaterialDefault(); Material mat = LoadMaterialDefault();
mat.maps[MATERIAL_MAP_DIFFUSE].texture = atlas; mat.maps[MATERIAL_MAP_DIFFUSE].texture = atlas;
// --- Initialize camera --- // --- Player setup ---
Camera3D camera = { 0 }; Player player = {
camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Initial camera position .mapPosition = (Vector3){ 1.0f, 67.0f, 1.0f },
camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Looking toward origin .playerOrientation = (Vector2){ 0.0f, 0.0f },
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Y-up world .moveSpeed = 10.0f
camera.fovy = 90.0f; // Field of view };
camera.projection = CAMERA_PERSPECTIVE; // Use perspective projection
player.camera.fovy = 90.0f;
player.camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
player.camera.projection = CAMERA_PERSPECTIVE;
SetTargetFPS(60); SetTargetFPS(60);
// --- Main game loop --- bool paused = false;
while (!WindowShouldClose()) { RaycastHit hit;
screenWidth = GetScreenWidth(); int blockSelection = BLOCK_SAND;
screenHeight = GetScreenHeight();
BeginDrawing();
ClearBackground(RAYWHITE);
// --- Handle jumping to fullscreen mode with F11 --- while (!WindowShouldClose()) {
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
// Toggle fullscreen
if (IsKeyPressed(KEY_F11)) { if (IsKeyPressed(KEY_F11)) {
ToggleFullscreen(); ToggleFullscreen();
Vector2 center = { GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f }; SetMousePosition(screenWidth / 2, screenHeight / 2);
SetMousePosition(center.x, center.y);
} }
// --- Handle pausing --- // Pause toggle
if (IsKeyPressed(KEY_ESCAPE)) { if (IsKeyPressed(KEY_ESCAPE)) {
paused = !paused; paused = !paused;
if (paused) EnableCursor(); paused ? EnableCursor() : DisableCursor();
else DisableCursor();
} }
// --- Handle selecting blocks --- // Temporary block selection hotkeys
if (IsKeyPressed(KEY_ONE)) blockSelection = BLOCK_STONE; if (IsKeyPressed(KEY_ONE)) blockSelection = BLOCK_STONE;
if (IsKeyPressed(KEY_TWO)) blockSelection = BLOCK_DIRT; if (IsKeyPressed(KEY_TWO)) blockSelection = BLOCK_DIRT;
if (IsKeyPressed(KEY_THREE)) blockSelection = BLOCK_GRASS; if (IsKeyPressed(KEY_THREE)) blockSelection = BLOCK_GRASS;
@ -102,54 +77,36 @@ int main(void) {
if (IsKeyPressed(KEY_EIGHT)) blockSelection = BLOCK_PLANK; if (IsKeyPressed(KEY_EIGHT)) blockSelection = BLOCK_PLANK;
if (!paused) { if (!paused) {
// --- Update camera and direction --- UpdatePlayer(&player);
hit = GetPlayerRaycastHit(&player, &chunk, 10.0f);
HandleBlockInteraction(&player, &chunk, hit, blockSelection);
UpdateFreeCamera(&camera, 10.0f, &cameraYaw, &cameraPitch); // Move camera with user input if (hit.hit && (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonPressed(MOUSE_RIGHT_BUTTON))) {
UnloadMesh(chunkMesh);
chunkMesh = GenerateChunkMesh(&chunk);
}
}
BeginDrawing();
ClearBackground(RAYWHITE);
// --- Raycasting from screen center --- BeginMode3D(player.camera);
Vector2 screenCenter = { screenWidth / 2.0f, screenHeight / 2.0f };
// This is where we grab the ray...
// Ray ray = GetMouseRay(screenCenter, camera);
Vector3 camDir = {
cosf(cameraPitch) * sinf(cameraYaw),
sinf(cameraPitch),
cosf(cameraPitch) * cosf(cameraYaw)
};
Ray ray = {
.position = camera.position,
.direction = Vector3Normalize(camDir)
};
hit = RaycastChunk(&chunk, ray.position, ray.direction, 10.0f);
// --- Begin 3D rendering ---
BeginMode3D(camera);
DrawMesh(chunkMesh, mat, MatrixIdentity()); DrawMesh(chunkMesh, mat, MatrixIdentity());
if (hit.hit) { if (hit.hit) {
// Draw a wireframe cube where the ray hit
DrawCubeWires(Vector3Add(hit.position, (Vector3){0.5f, 0.5f, 0.5f}), 1.02f, 1.02f, 1.02f, BLACK); DrawCubeWires(Vector3Add(hit.position, (Vector3){0.5f, 0.5f, 0.5f}), 1.02f, 1.02f, 1.02f, BLACK);
//DrawFaceHighlight(hit.position, hit.normal); // Highlight the specific face hit //DrawFaceHighlight(hit.position, hit.normal);
} }
// Draw a lil debug cube where the player is looking.
//Vector3 hitPoint = Vector3Add(ray.position, Vector3Scale(ray.direction, hit.t));
//DrawCubeWires(hitPoint, 0.05f, 0.05f, 0.05f, RED);
EndMode3D(); EndMode3D();
// --- Draw crosshair in screen center --- // Draw crosshair
DrawLine(screenWidth / 2 - 5, screenHeight / 2, screenWidth / 2 + 5, screenHeight / 2, DARKGRAY); DrawLine(screenWidth / 2 - 5, screenHeight / 2, screenWidth / 2 + 5, screenHeight / 2, DARKGRAY);
DrawLine(screenWidth / 2, screenHeight / 2 - 5, screenWidth / 2, screenHeight / 2 + 5, DARKGRAY); DrawLine(screenWidth / 2, screenHeight / 2 - 5, screenWidth / 2, screenHeight / 2 + 5, DARKGRAY);
// -- Draw debug info --- // Debug info
DrawText(TextFormat("Facing: %s", GetCompassDirection(cameraYaw)), 10, 10, 20, DARKGRAY); DrawText(TextFormat("Yaw: %.1f Pitch: %.1f", player.playerOrientation.x * RAD2DEG, player.playerOrientation.y * RAD2DEG), 10, 10, 20, DARKGRAY);
DrawText(TextFormat("Yaw: %.1f° Pitch: %.1f°", cameraYaw * RAD2DEG, cameraPitch * RAD2DEG), 10, 30, 20, GRAY);
}
// --- Draw pause menu if paused ---
if (paused) { if (paused) {
DrawRectangle(0, 0, screenWidth, screenHeight, Fade(DARKGRAY, 0.5f)); DrawRectangle(0, 0, screenWidth, screenHeight, Fade(DARKGRAY, 0.5f));
DrawText("Paused", screenWidth / 2 - MeasureText("Paused", 40) / 2, screenHeight / 2 - 20, 40, RAYWHITE); DrawText("Paused", screenWidth / 2 - MeasureText("Paused", 40) / 2, screenHeight / 2 - 20, 40, RAYWHITE);
@ -157,38 +114,10 @@ int main(void) {
} }
EndDrawing(); EndDrawing();
// TODO: Shoudn't all this block handling be handled in playerController.c?
// --- Handle block removal (left click) ---
if (hit.hit && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
chunk.blocks[(int)hit.position.x][(int)hit.position.y][(int)hit.position.z].type = BLOCK_AIR;
chunkMesh = GenerateChunkMesh(&chunk); // Rebuild mesh after change
}
// --- Handle block placement (right click) ---
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);
// Bounds check
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;
chunkMesh = GenerateChunkMesh(&chunk);
printf("Hit at (%f %f %f), normal (%f %f %f), placing at (%d %d %d)\n",
hit.position.x, hit.position.y, hit.position.z,
hit.normal.x, hit.normal.y, hit.normal.z,
px, py, pz);
}
}
} }
// --- Cleanup --- // --- Cleanup ---
SaveChunk(&chunk, "saves/chunk_0_0_0.dat");
UnloadTexture(atlas); UnloadTexture(atlas);
UnloadMesh(chunkMesh); UnloadMesh(chunkMesh);
UnloadMaterial(mat); UnloadMaterial(mat);