Better player encapsulation. World persistence. Cleaned up CPU and GPU memory leaks.
This commit is contained in:
parent
585f72a4bd
commit
701c72a2a5
@ -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 |
@ -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
|
||||||
|
|||||||
@ -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
0
saves/.gitkeep
Normal 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;
|
||||||
|
|||||||
@ -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 (0–3),
|
// Returns the UV coordinate for a given tile index and corner index (0–3),
|
||||||
/// 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -1,60 +1,48 @@
|
|||||||
// playerController.c
|
// playerController.c
|
||||||
// Player controller for Voxelthing
|
#include "raylib.h"
|
||||||
|
#include "raymath.h"
|
||||||
|
#include "playerController.h"
|
||||||
|
#include "blockTypes.h"
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#include"raylib.h"
|
void UpdatePlayer(Player *player) {
|
||||||
#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();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user