first commit
This commit is contained in:
commit
f4cfde8cf9
15
README.md
Normal file
15
README.md
Normal 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
BIN
assets/TextureAtlas.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.4 KiB |
BIN
assets/TextureAtlas.xcf
Normal file
BIN
assets/TextureAtlas.xcf
Normal file
Binary file not shown.
18
include/atlasDefinitions.h
Normal file
18
include/atlasDefinitions.h
Normal 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
23
include/blockTypes.h
Normal 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
16
include/chunkGenerator.h
Normal 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
11
include/chunkRenderer.h
Normal 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
36
include/chunkStructures.h
Normal 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
|
||||||
22
include/playerController.h
Normal file
22
include/playerController.h
Normal 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
30
makefile
Normal 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
75
source/blockTypes.c
Normal 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
25
source/chunkGenerator.c
Normal 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
147
source/chunkRenderer.c
Normal 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 (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;
|
||||||
|
}
|
||||||
|
|
||||||
60
source/chunkStructures.c
Normal file
60
source/chunkStructures.c
Normal 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
182
source/playerController.c
Normal 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
156
source/voxelThing.c
Normal 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;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user