SimpleMiner is a 3D voxel game built in C++17 on the custom Eurekiel Engine (DirectX 11). The project covers voxel engine development: multithreaded chunk streaming, procedural world generation with biome transitions, a Minecraft-compatible JSON model and blockstate pipeline, flood-fill lighting, AABB physics, and face-culling mesh generation.
Lighting and atmosphere
Core systems
Multithreaded chunk pipeline
Chunks are loaded and generated through a job-based worker pool: background threads handle procedural terrain generation and disk reads, while the main thread manages mesh assembly and GPU buffer uploads. A distance-sorted priority queue processes chunks closest to the player first, supporting concurrent generation without stalling the render thread.
graph LR
A[Main Thread] -->|ActivateChunk| B[Pending Queue]
B -->|Distance Sort| C[Worker Threads]
C -->|GenerateJob| D[Terrain Data]
C -->|LoadJob| E[Disk Data]
D --> F[Completed Queue]
E --> F
F -->|ProcessCompleted| A
A -->|RebuildMesh ≤4/frame| G[GPU Upload]
Procedural world generation and biome blending
Terrain generation combines 2D Perlin noise for surface heightmaps with 3D noise for overhangs and cave tunnels. Biomes (forest, plains, snow, taiga, jungle, desert, and ice mountains) are selected from temperature and humidity noise fields, using height interpolation across boundaries to smooth transitions. Vegetation and trees are placed using a secondary noise pass with biome-specific density rules.


Flood-fill lighting algorithm
Lighting uses a breadth-first search (BFS) propagation model across adjacent block faces in six directions. Sky light propagates downward from the top of each chunk column, while block light spreads outward from emissive light sources. Both channels pack into a single byte per block (4 bits for sky light, 4 bits for block light). When blocks are placed or broken, a localized flood pass recalculates only the affected volume rather than relighting whole chunks.
AABB physics and collision
Entity movement supports three modes: walking with gravity and collision, flying with collision only, and noclip. Collision tests use 12 sample rays across three height levels (feet, waist, and head) around four bounding box corners, resolving collisions on each axis independently to prevent corner snagging. Four downward rays handle ground detection.
Resource pipeline
The resource pipeline is modeled directly on Minecraft’s resource and data pack specification, allowing standard blockstate JSONs, models, and textures to be imported into the engine.
Texture atlas and model inheritance
During startup, the engine indexes registered block textures and packs them into a single GPU texture atlas with generated UV bounds. Block models are defined in JSON, supporting parent-child model inheritance (for example, block/cube_column inheriting from block/cube) and variable texture overrides. The loader traverses the inheritance tree and resolves texture identifiers to produce final quad vertices.

Block state machine: stairs implementation
Stairs use 40 block state permutations generated from three properties: facing (4), half (2), and shape (5). The pipeline from placement to geometry evaluates in five steps:
Step 1: placement orientation
When a player places a stair block, StairsBlock::GetStateForPlacement determines facing and half from the placement context:
- Facing (north, south, east, west) is taken from the player’s horizontal look direction.
- Half (bottom or top) depends on the clicked surface. Clicking a bottom face or the upper half of a side face produces an upside-down stair (
top), while clicking a top face produces a standard stair (bottom).
// Facing = player look direction
Direction facing = ctx.GetHorizontalFacing();
// Half = based on which face was clicked
if (ctx.clickedFace == Direction::UP)
half = HalfType::BOTTOM;
else if (ctx.clickedFace == Direction::DOWN)
half = HalfType::TOP;
else
half = ctx.IsTopHalf() ? HalfType::TOP : HalfType::BOTTOM;
Step 2: shape calculation and neighbor connections
After setting facing and half, the engine inspects neighboring blocks along the facing axis to choose a corner shape:
- Check front neighbor: If it is a stair with matching
halfand a perpendicular facing direction, it forms an outer corner (outer_leftorouter_rightbased on turn direction). - Check back neighbor: If it matches with perpendicular facing, it forms an inner corner (
inner_leftorinner_right). - Default: If neither condition matches, the shape defaults to
straight.
flowchart TD
P[Player Places Stair] --> F[Facing = Player Look Dir]
P --> H[Half = Click Face Logic]
F --> SC[Shape Calculation]
H --> SC
SC --> FN{Front Neighbor<br/>is Stair?}
FN -->|Same half +<br/>perpendicular facing| OC[Outer Corner]
OC --> OL[outer_left / outer_right<br/>based on turn direction]
FN -->|No| BN{Back Neighbor<br/>is Stair?}
BN -->|Same half +<br/>perpendicular facing| IC[Inner Corner]
IC --> IL[inner_left / inner_right<br/>based on turn direction]
BN -->|No| ST[straight]
When adjacent blocks update, OnNeighborChanged recalculates shapes to adapt existing stairs dynamically.


Step 3: block data definition (YAML)
Each stair variant registers properties through a YAML definition:
# oak_stairs.yml
display_name: Oak Stairs
block_class: StairsBlock
properties:
facing: [north, south, east, west]
half: [bottom, top]
shape: [straight, inner_left, inner_right, outer_left, outer_right]
default_state:
facing: north
half: bottom
shape: straight
opaque: false
full_block: false
BlockRegistry reads this file and calls GenerateBlockStates() to build all 40 unique state instances.
Step 4: blockstate model mapping (JSON)
Blockstate JSON files map property sets to models and rotations:
{
"variants": {
"facing=east,half=bottom,shape=straight": {
"model": "simpleminer:block/oak_stairs"
},
"facing=east,half=bottom,shape=inner_right": {
"model": "simpleminer:block/oak_inner_stairs"
},
"facing=east,half=bottom,shape=outer_left": {
"model": "simpleminer:block/oak_outer_stairs",
"uvlock": true, "y": 270
},
"facing=east,half=top,shape=straight": {
"model": "simpleminer:block/oak_stairs",
"uvlock": true, "x": 180
}
}
}
The x and y fields specify model rotations, while uvlock: true keeps textures aligned without rotation distortion.
Step 5: model inheritance and geometry templates
Stairs inherit from three base geometry templates:
| Shape | Engine Base Model | Geometry |
|---|---|---|
| straight | block/stairs | Full bottom slab and half-width upper step (2 cuboids) |
| inner_left/right | block/inner_stairs | Full bottom slab and L-shaped upper step (3 cuboids) |
| outer_left/right | block/outer_stairs | Full bottom slab and quarter upper step (2 cuboids) |
Specific stairs like oak_stairs inherit the base geometry and specify texture references:
{
"parent": "block/stairs",
"textures": {
"bottom": "simpleminer:block/oak_planks",
"top": "simpleminer:block/oak_planks",
"side": "simpleminer:block/oak_planks"
}
}
Adding a stair type requires a texture reference, YAML file, and blockstate definition without modifying engine code.


Hidden-face removal and mesh optimization
The mesh builder skips interior faces between adjacent opaque blocks. For partial blocks like stairs and slabs, occlusion checks inspect the neighbor’s voxel collision shape rather than relying solely on opacity flags, keeping visible geometry intact while culling obscured polygons.

Design decisions
Data-driven block authoring
Adding new block variants requires only JSON models, blockstate maps, and texture assets. The registry, texture atlas generator, and mesh builder process assets without altering C++ engine logic.
Layered engine and game architecture
The project enforces a boundary between Eurekiel Engine primitives (chunks, world structures, lighting, rendering) and game logic (terrain generators, inventory rules, and block definitions).
Bounded resource usage
Systems operate within performance budgets: light values use a packed byte per block, chunk queues sort by proximity, mesh uploads are throttled to four per frame, and face culling discards unexposed geometry.