From f4cfde8cf9301642cbdd93a1c1250dc122d8b81f Mon Sep 17 00:00:00 2001 From: Jake Date: Mon, 26 May 2025 20:59:26 -0400 Subject: [PATCH] first commit --- README.md | 15 +++ assets/TextureAtlas.png | Bin 0 -> 5578 bytes assets/TextureAtlas.xcf | Bin 0 -> 11947 bytes include/atlasDefinitions.h | 18 ++++ include/blockTypes.h | 23 +++++ include/chunkGenerator.h | 16 ++++ include/chunkRenderer.h | 11 +++ include/chunkStructures.h | 36 ++++++++ include/playerController.h | 22 +++++ makefile | 30 ++++++ source/blockTypes.c | 75 +++++++++++++++ source/chunkGenerator.c | 25 +++++ source/chunkRenderer.c | 147 ++++++++++++++++++++++++++++++ source/chunkStructures.c | 60 ++++++++++++ source/playerController.c | 182 +++++++++++++++++++++++++++++++++++++ source/voxelThing.c | 156 +++++++++++++++++++++++++++++++ 16 files changed, 816 insertions(+) create mode 100644 README.md create mode 100644 assets/TextureAtlas.png create mode 100644 assets/TextureAtlas.xcf create mode 100644 include/atlasDefinitions.h create mode 100644 include/blockTypes.h create mode 100644 include/chunkGenerator.h create mode 100644 include/chunkRenderer.h create mode 100644 include/chunkStructures.h create mode 100644 include/playerController.h create mode 100644 makefile create mode 100644 source/blockTypes.c create mode 100644 source/chunkGenerator.c create mode 100644 source/chunkRenderer.c create mode 100644 source/chunkStructures.c create mode 100644 source/playerController.c create mode 100644 source/voxelThing.c diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b06880 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# voxelThing +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. + +Code is rough and messy and not much is implemented yet. + +Depends on Raylib. + +## Build Instructions: + +1) Install Raylib. +2) Clone the repo. +3) Build by running make. + diff --git a/assets/TextureAtlas.png b/assets/TextureAtlas.png new file mode 100644 index 0000000000000000000000000000000000000000..50cca6d07acf39ad97432d8a01042c16c0742a6d GIT binary patch literal 5578 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 gm(%n$HA3{@}qHx7102<)6@Bjb+ literal 0 HcmV?d00001 diff --git a/include/atlasDefinitions.h b/include/atlasDefinitions.h new file mode 100644 index 0000000..a2ba30b --- /dev/null +++ b/include/atlasDefinitions.h @@ -0,0 +1,18 @@ +// atlasDefinitions.h +// This header file contians define statements linking the names of each texture in the atlas to its index. +// I feel like there's probably a better way to link these things together, but this will work for now.' + +#ifndef ATLAS_DEFINITIONS_H +#define ATLAS_DEFINITIONS_H + +#define TILE_STONE 0 +#define TILE_DIRT 1 +#define TILE_GRASS_TOP 2 +#define TILE_GRASS_SIDE 3 +#define TILE_SAND 4 +#define TILE_GRAVEL 5 +#define TILE_LOG_TOP 6 +#define TILE_LOG_SIDE 7 +#define TILE_LEAF 8 + +#endif diff --git a/include/blockTypes.h b/include/blockTypes.h new file mode 100644 index 0000000..36eb485 --- /dev/null +++ b/include/blockTypes.h @@ -0,0 +1,23 @@ +// blockTypes.h +#ifndef BLOCK_TYPES_H +#define BLOCKTYPES_H + +// Definitions for Block IDs (notes are texture atlas indicies) +#define BLOCK_AIR 0 // No texture. +#define BLOCK_STONE 1 // 0 +#define BLOCK_DIRT 2 // 1 +#define BLOCK_GRASS 3 // top, 2, sides 3, bottom 1 +#define BLOCK_SAND 4 // 4 +#define BLOCK_GRAVEL 5 // 5 +#define BLOCK_LOG 6 // top 6, sides 7, bottom 6 +#define BLOCK_LEAF 7 // 8 + +typedef struct { + int id; + const char *name; + int faceTiles[6]; // Index by face: 0=-X, 1=+X, 2=-Y, 3=+Y, 4=-Z, 5=+Z +} BlockType; + +const BlockType *GetBlockType(int blockID); + +#endif diff --git a/include/chunkGenerator.h b/include/chunkGenerator.h new file mode 100644 index 0000000..55fac21 --- /dev/null +++ b/include/chunkGenerator.h @@ -0,0 +1,16 @@ +// Chunk generation functions for voxelThing. +#ifndef CHUNK_GENERATOR_H +#define CHUNK_GENERATOR_H + +#include "chunkStructures.h" + +// Function for initializing a chunk as a typical sort of flatworld chunk. +void GenerateFlatChunk(Chunk *chunk) ; + +// Function to initialize the chunk with dirt. +void GenerateChunk(Chunk *chunk); + +// Function for initializing a chunk sparsely. (still dirt) +void GenerateSparseChunk(Chunk *chunk); + +#endif diff --git a/include/chunkRenderer.h b/include/chunkRenderer.h new file mode 100644 index 0000000..c3b8db0 --- /dev/null +++ b/include/chunkRenderer.h @@ -0,0 +1,11 @@ +// chunkRenderer.h +#ifndef CHUNK_RENDERER_H +#define CHUNK_RENDERER_H + +#include "raylib.h" +#include "chunkStructures.h" + +Mesh GenerateChunkMesh(Chunk *chunk); +Vector2 GetTileUV(int tileIndex, int corner); + +#endif diff --git a/include/chunkStructures.h b/include/chunkStructures.h new file mode 100644 index 0000000..c4d4a37 --- /dev/null +++ b/include/chunkStructures.h @@ -0,0 +1,36 @@ +// chunkStructures.h + +#ifndef CHUNK_STRUCTURES_H +#define CHUNK_STRUCTURES_H + +#include "raylib.h" + +#define CHUNK_SIZE_X 16 +#define CHUNK_SIZE_Y 16 +#define CHUNK_SIZE_Z 16 + + +typedef struct { + int type; // 0 = air, 1 = dirt, etc. +} Block; + +// 6 directions for checking neighbors: +/-X, +/-Y, +/-Z +static const int faceOffsets[6][3] = { + { -1, 0, 0 }, // left + { 1, 0, 0 }, // right + { 0, -1, 0 }, // bottom + { 0, 1, 0 }, // top + { 0, 0, -1 }, // back + { 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. +int IsBlockFaceExposed(Chunk *chunk, int x, int y, int z, int dir); + +void PlaceTreeAt(Chunk *chunk, int x, int y, int z) ; + +#endif diff --git a/include/playerController.h b/include/playerController.h new file mode 100644 index 0000000..46e2c69 --- /dev/null +++ b/include/playerController.h @@ -0,0 +1,22 @@ +// playerController.h +#ifndef PLAYER_CONTROLLER_H +#define PLAYER_CONTROLLER_H + +#include "raylib.h" +#include "chunkStructures.h" + +typedef struct { + bool hit; + Vector3 position; + Vector3 normal; + int blockID; + float t; +} RaycastHit; + +RaycastHit RaycastChunk(const Chunk *chunk, Vector3 origin, Vector3 direction, float maxDistance); + +void UpdateFreeCamera(Camera3D *cam, float speed, float *yawOut, float *pitchOut); + +void DrawFaceHighlight(Vector3 blockPos, Vector3 normal); + +#endif diff --git a/makefile b/makefile new file mode 100644 index 0000000..1d0d3d6 --- /dev/null +++ b/makefile @@ -0,0 +1,30 @@ +# Compiler and flags +CC = gcc +CFLAGS = -Wall -Wextra -std=c99 -O2 -Iinclude +LDFLAGS = -lraylib -lm -ldl -lpthread -lGL -lrt -lX11 + +# Directories +SRC_DIR = source +BIN_DIR = bin +BUILD_DIR = build + +# Files +TARGET = $(BIN_DIR)/voxelThing +SRC = $(wildcard $(SRC_DIR)/*.c) +OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRC)) + +# Rules +all: $(TARGET) + +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) -c $< -o $@ + +$(TARGET): $(OBJ) + @mkdir -p $(BIN_DIR) + $(CC) $(OBJ) -o $@ $(LDFLAGS) + +clean: + rm -rf $(BUILD_DIR) $(BIN_DIR) + +.PHONY: all clean diff --git a/source/blockTypes.c b/source/blockTypes.c new file mode 100644 index 0000000..fa9ce4f --- /dev/null +++ b/source/blockTypes.c @@ -0,0 +1,75 @@ +// blockTypes.c +// Definitions for which blocks exist in voxelThing, as well as the tile from the atlas for each face of the block. +#include "blockTypes.h" +#include "atlasDefinitions.h" + +static const BlockType blockTable[] = { + [BLOCK_AIR] = { + .id = BLOCK_AIR, + .name = "Air", + .faceTiles = { -1, -1, -1, -1, -1, -1 } + }, + [BLOCK_STONE] = { + .id = BLOCK_STONE, + .name = "Stone", + .faceTiles = { TILE_STONE, TILE_STONE, TILE_STONE, TILE_STONE, TILE_STONE, TILE_STONE } + }, + [BLOCK_DIRT] = { + .id = BLOCK_DIRT, + .name = "Dirt", + .faceTiles = { TILE_DIRT, TILE_DIRT, TILE_DIRT, TILE_DIRT, TILE_DIRT, TILE_DIRT } + }, + [BLOCK_GRASS] = { + .id = BLOCK_GRASS, + .name = "Grass", + .faceTiles = { + TILE_GRASS_SIDE, // -X + TILE_GRASS_SIDE, // +X + TILE_DIRT, // -Y + TILE_GRASS_TOP, // +Y + TILE_GRASS_SIDE, // -Z + TILE_GRASS_SIDE // +Z + } + }, + [BLOCK_SAND] = { + .id = BLOCK_SAND, + .name = "Sand", + .faceTiles = { TILE_SAND, TILE_SAND, TILE_SAND, TILE_SAND, TILE_SAND, TILE_SAND } + }, + [BLOCK_GRAVEL] = { + .id = BLOCK_GRASS, + .name = "Grass", + .faceTiles = { + TILE_GRASS_SIDE, // -X + TILE_GRASS_SIDE, // +X + TILE_DIRT, // -Y + TILE_GRASS_TOP, // +Y + TILE_GRASS_SIDE, // -Z + TILE_GRASS_SIDE // +Z + } + }, + [BLOCK_LOG] = { + .id = BLOCK_LOG, + .name = "Log", + .faceTiles = { + TILE_LOG_SIDE, // -X + TILE_LOG_SIDE, // +X + TILE_LOG_TOP, // -Y + TILE_LOG_TOP, // +Y + TILE_LOG_SIDE, // -Z + TILE_LOG_SIDE // +Z + } + }, + [BLOCK_LEAF] = { + .id = BLOCK_LEAF, + .name = "Leaves", + .faceTiles = { TILE_LEAF, TILE_LEAF, TILE_LEAF, TILE_LEAF, TILE_LEAF, TILE_LEAF } + }, +}; + +const BlockType *GetBlockType(int blockID) { + if (blockID < 1 || blockID >= (sizeof(blockTable) / sizeof(blockTable[0]))) { + return &blockTable[BLOCK_AIR]; + } + return &blockTable[blockID]; +} diff --git a/source/chunkGenerator.c b/source/chunkGenerator.c new file mode 100644 index 0000000..49af4f4 --- /dev/null +++ b/source/chunkGenerator.c @@ -0,0 +1,25 @@ +// chunkGenerator.c +// These functions fill a chunk with blocks and are placeholders for fancier ones. + +#include "raylib.h" +#include "chunkGenerator.h" +#include "blockTypes.h" + +// Fill a chunk with normalish Minecraft style flatworld terrain. A few layers of stone on the bottom, a few layers of dirt, and a layer of grass on top. +void GenerateFlatChunk(Chunk *chunk) { + for (int x = 0; x < CHUNK_SIZE_X; x++) { + for (int z = 0; z < CHUNK_SIZE_Z; z++) { + for (int y = 0; y < CHUNK_SIZE_Y; y++) { + if (y < 3) { + chunk->blocks[x][y][z].type = BLOCK_STONE; + } else if (y < 7) { + chunk->blocks[x][y][z].type = BLOCK_DIRT; + } else if (y == 7) { + chunk->blocks[x][y][z].type = BLOCK_GRASS; + } else { + chunk->blocks[x][y][z].type = BLOCK_AIR; + } + } + } + } +} diff --git a/source/chunkRenderer.c b/source/chunkRenderer.c new file mode 100644 index 0000000..4839635 --- /dev/null +++ b/source/chunkRenderer.c @@ -0,0 +1,147 @@ +// chunkRenderer.c +// 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 +#include "raylib.h" +#include "blockTypes.h" +#include "atlasDefinitions.h" +#include "chunkRenderer.h" + +#define TILE_SIZE 16 +#define ATLAS_SIZE 256 +#define ATLAS_TILES_PER_ROW (ATLAS_SIZE / TILE_SIZE) + +/// 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. +Vector2 GetTileUV(int tile, int corner) { + int tileX = tile % ATLAS_TILES_PER_ROW; + int tileY = tile / ATLAS_TILES_PER_ROW; + + float u = (float)(tileX * TILE_SIZE) / ATLAS_SIZE; + float v = (float)(tileY * TILE_SIZE) / ATLAS_SIZE; + float duv = (float)TILE_SIZE / ATLAS_SIZE; + + switch (corner) { + case 0: return (Vector2){ u, v }; + case 1: return (Vector2){ u + duv, v }; + case 2: return (Vector2){ u + duv, v + duv }; + case 3: return (Vector2){ u, v + duv }; + default: return (Vector2){ 0.0f, 0.0f }; // Should not happen + } +} + +/// Generates a mesh for the given chunk by stitching together visible block faces. +Mesh GenerateChunkMesh(Chunk *chunk) { + const int maxFaces = CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z * 6; + const int maxVerts = maxFaces * 4; // 4 verts per face + const int maxIndices = maxFaces * 6; // 2 triangles per face + + Vector3 *vertices = malloc(sizeof(Vector3) * maxVerts); + Vector3 *normals = malloc(sizeof(Vector3) * maxVerts); + Vector2 *texcoords = malloc(sizeof(Vector2) * maxVerts); + unsigned short *indices = malloc(sizeof(unsigned short) * maxIndices); + + int vertCount = 0; + int indexCount = 0; + + for (int x = 0; x < CHUNK_SIZE_X; x++) { + for (int y = 0; y < CHUNK_SIZE_Y; y++) { + for (int z = 0; z < CHUNK_SIZE_Z; z++) { + + Block block = chunk->blocks[x][y][z]; + if (block.type == 0) continue; // Skip air blocks + + Vector3 min = { x, y, z }; + Vector3 max = { x + 1.0f, y + 1.0f, z + 1.0f }; + + for (int dir = 0; dir < 6; dir++) { + if (!IsBlockFaceExposed(chunk, x, y, z, dir)) continue; + + Vector3 face[4]; + + switch (dir) { + case 0: // -X + face[0] = (Vector3){ min.x, max.y, min.z }; + face[1] = (Vector3){ min.x, max.y, max.z }; + face[2] = (Vector3){ min.x, min.y, max.z }; + face[3] = (Vector3){ min.x, min.y, min.z }; + break; + case 1: // +X + face[0] = (Vector3){ max.x, max.y, max.z }; + face[1] = (Vector3){ max.x, max.y, min.z }; + face[2] = (Vector3){ max.x, min.y, min.z }; + face[3] = (Vector3){ max.x, min.y, max.z }; + break; + case 2: // -Y + face[0] = (Vector3){ min.x, min.y, max.z }; + face[1] = (Vector3){ max.x, min.y, max.z }; + face[2] = (Vector3){ max.x, min.y, min.z }; + face[3] = (Vector3){ min.x, min.y, min.z }; + break; + case 3: // +Y + face[0] = (Vector3){ min.x, max.y, min.z }; + face[1] = (Vector3){ max.x, max.y, min.z }; + face[2] = (Vector3){ max.x, max.y, max.z }; + face[3] = (Vector3){ min.x, max.y, max.z }; + break; + case 4: // -Z + face[0] = (Vector3){ max.x, max.y, min.z }; + face[1] = (Vector3){ min.x, max.y, min.z }; + face[2] = (Vector3){ min.x, min.y, min.z }; + face[3] = (Vector3){ max.x, min.y, min.z }; + break; + case 5: // +Z + face[0] = (Vector3){ min.x, max.y, max.z }; + face[1] = (Vector3){ max.x, max.y, max.z }; + face[2] = (Vector3){ max.x, min.y, max.z }; + face[3] = (Vector3){ min.x, min.y, max.z }; + break; + } + + const BlockType *bt = GetBlockType(block.type); + int tileIndex = bt->faceTiles[dir]; + + Vector3 normal = {0}; + switch (dir) { + case 0: normal = (Vector3){ -1.0f, 0.0f, 0.0f }; break; + case 1: normal = (Vector3){ 1.0f, 0.0f, 0.0f }; break; + case 2: normal = (Vector3){ 0.0f, -1.0f, 0.0f }; break; + case 3: normal = (Vector3){ 0.0f, 1.0f, 0.0f }; break; + case 4: normal = (Vector3){ 0.0f, 0.0f, -1.0f }; break; + case 5: normal = (Vector3){ 0.0f, 0.0f, 1.0f }; break; + } + + for (int i = 0; i < 4; i++) { + vertices[vertCount] = face[i]; + texcoords[vertCount] = GetTileUV(tileIndex, i); + normals[vertCount] = normal; + vertCount++; + } + + indices[indexCount++] = vertCount - 4; + indices[indexCount++] = vertCount - 2; + indices[indexCount++] = vertCount - 3; + indices[indexCount++] = vertCount - 4; + indices[indexCount++] = vertCount - 1; + indices[indexCount++] = vertCount - 2; + } + } + } + } + + Mesh mesh = {0}; + mesh.vertexCount = vertCount; + mesh.triangleCount = indexCount / 3; + + mesh.vertices = (float *)vertices; + mesh.texcoords = (float *)texcoords; + mesh.normals = (float *)normals; + mesh.indices = indices; + + UploadMesh(&mesh, false); + return mesh; +} + diff --git a/source/chunkStructures.c b/source/chunkStructures.c new file mode 100644 index 0000000..3154393 --- /dev/null +++ b/source/chunkStructures.c @@ -0,0 +1,60 @@ +// chunkStructures.c +// This file contains functions for placing structures inside chunks, as well as the definitions for chunk datastructures... +// TODO: Should probably keep datastructures and chunk structures separate. + +#include "chunkStructures.h" +#include "blockTypes.h" +#include "raylib.h" +#include "rlgl.h" +#include +#include + +// Places a tree at the position specified by x, y, z +void PlaceTreeAt(Chunk *chunk, int x, int y, int z) { + // Trunk (6 blocks tall) + for (int i = 1; i <= 6; i++) { + if (y + i >= CHUNK_SIZE_Y) break; + chunk->blocks[x][y + i][z].type = BLOCK_LOG; + } + + // Canopy (2 layers of leaves) + for (int dy = 4; dy <= 6; dy++) { + for (int dx = -2; dx <= 2; dx++) { + for (int dz = -2; dz <= 2; dz++) { + int cx = x + dx; + int cy = y + dy; + int cz = z + dz; + + if (cx < 0 || cx >= CHUNK_SIZE_X || + cy < 0 || cy >= CHUNK_SIZE_Y || + cz < 0 || cz >= CHUNK_SIZE_Z) + continue; + + // Keep the center clear above trunk + if (dx == 0 && dz == 0 && dy == 4) continue; + + // Slightly sparser at corners + if (abs(dx) == 2 && abs(dz) == 2) continue; + + chunk->blocks[cx][cy][cz].type = BLOCK_LEAF; + } + } + } +} + +int IsBlockFaceExposed(Chunk *chunk, int x, int y, int z, int dir) { + int nx = x + faceOffsets[dir][0]; + int ny = y + faceOffsets[dir][1]; + int nz = z + faceOffsets[dir][2]; + + if (nx < 0 || ny < 0 || nz < 0 || + nx >= CHUNK_SIZE_X || ny >= CHUNK_SIZE_Y || nz >= CHUNK_SIZE_Z) { + return 1; // face is exposed at chunk boundary + } + + return chunk->blocks[nx][ny][nz].type == 0; +} + + + + diff --git a/source/playerController.c b/source/playerController.c new file mode 100644 index 0000000..4760ee8 --- /dev/null +++ b/source/playerController.c @@ -0,0 +1,182 @@ +// 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(); +} + diff --git a/source/voxelThing.c b/source/voxelThing.c new file mode 100644 index 0000000..9af4648 --- /dev/null +++ b/source/voxelThing.c @@ -0,0 +1,156 @@ +// voxelThing.c +// A voxel chunk rendering doodad written in C with Raylib + +// External libraries +#include "raylib.h" +#include "raymath.h" +#include "rlgl.h" +#include "stdio.h" + +// Project files +#include "chunkStructures.h" +#include "chunkRenderer.h" +#include "blockTypes.h" +#include "playerController.h" +#include "chunkGenerator.h" + +float cameraYaw = 0; +float cameraPitch = 0; + +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)"; +} + +int main(void) { + // --- Screen setup --- + const int screenWidth = 800; + const int screenHeight = 600; + InitWindow(screenWidth, screenHeight, "VoxelThing"); + DisableCursor(); // Lock mouse to window for FPS-style camera + + // --- World generation --- + Chunk chunk; + GenerateFlatChunk(&chunk); + PlaceTreeAt(&chunk, 8, 7, 8); + Mesh chunkMesh = GenerateChunkMesh(&chunk); + + // --- Load textures and materials --- + Texture2D atlas = LoadTexture("assets/TextureAtlas.png"); + Material mat = LoadMaterialDefault(); + mat.maps[MATERIAL_MAP_DIFFUSE].texture = atlas; + + // --- Initialize camera --- + Camera3D camera = { 0 }; + camera.position = (Vector3){ 10.0f, 10.0f, 10.0f }; // Initial camera position + camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; // Looking toward origin + camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Y-up world + camera.fovy = 45.0f; // Field of view + camera.projection = CAMERA_PERSPECTIVE; // Use perspective projection + + SetTargetFPS(60); + + // --- Main game loop --- + while (!WindowShouldClose()) { + BeginDrawing(); + ClearBackground(RAYWHITE); + + // --- Update camera and direction --- + + UpdateFreeCamera(&camera, 10.0f, &cameraYaw, &cameraPitch); // Move camera with user input + + + // --- Raycasting from screen center --- + 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) + }; + RaycastHit hit = RaycastChunk(&chunk, ray.position, ray.direction, 10.0f); + + // --- Begin 3D rendering --- + BeginMode3D(camera); + + DrawMesh(chunkMesh, mat, MatrixIdentity()); + + 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, RED); + DrawFaceHighlight(hit.position, hit.normal); // Highlight the specific face hit + } + // Draw debug ray. + camDir = Vector3Normalize(Vector3Subtract(camera.target, camera.position)); + Vector3 rayEnd = Vector3Add(camera.position, Vector3Scale(camDir, 10.0f)); + DrawLine3D(camera.position, rayEnd, PURPLE); + + Vector3 hitPoint = Vector3Add(ray.position, Vector3Scale(ray.direction, hit.t)); + DrawCubeWires(hitPoint, 0.05f, 0.05f, 0.05f, RED); + + EndMode3D(); + + Vector3 testPoint = Vector3Add(camera.position, Vector3Scale(camDir, 2.0f)); + Vector2 projected = GetWorldToScreen(testPoint, camera); + DrawCircleV(projected, 4, RED); + + // --- Draw crosshair in screen center --- + DrawLine(screenWidth/2 - 5, screenHeight/2, screenWidth/2 + 5, screenHeight/2, DARKGRAY); + DrawLine(screenWidth/2, screenHeight/2 - 5, screenWidth/2, screenHeight/2 + 5, DARKGRAY); + + // -- Draw debug info --- + DrawText(TextFormat("Facing: %s", GetCompassDirection(cameraYaw)), 10, 10, 20, DARKGRAY); + DrawText(TextFormat("Yaw: %.1f° Pitch: %.1f°", cameraYaw * RAD2DEG, cameraPitch * RAD2DEG), 10, 30, 20, GRAY); + + 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 = BLOCK_SAND; + 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 --- + UnloadTexture(atlas); + UnloadMesh(chunkMesh); + UnloadMaterial(mat); + CloseWindow(); + return 0; +}