first commit

This commit is contained in:
Jake 2025-05-26 20:59:26 -04:00
commit f4cfde8cf9
16 changed files with 816 additions and 0 deletions

15
README.md Normal file
View File

@ -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.

BIN
assets/TextureAtlas.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
assets/TextureAtlas.xcf Normal file

Binary file not shown.

View File

@ -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

23
include/blockTypes.h Normal file
View File

@ -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

16
include/chunkGenerator.h Normal file
View File

@ -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

11
include/chunkRenderer.h Normal file
View File

@ -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

36
include/chunkStructures.h Normal file
View File

@ -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

View File

@ -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

30
makefile Normal file
View File

@ -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

75
source/blockTypes.c Normal file
View File

@ -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];
}

25
source/chunkGenerator.c Normal file
View File

@ -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;
}
}
}
}
}

147
source/chunkRenderer.c Normal file
View File

@ -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 <stdlib.h>
#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 (03),
/// 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;
}

60
source/chunkStructures.c Normal file
View File

@ -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 <stdlib.h>
#include <string.h>
// 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;
}

182
source/playerController.c Normal file
View File

@ -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();
}

156
source/voxelThing.c Normal file
View File

@ -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;
}