Files
MC-Worldgen/worldgen-c/src/worldgen.c
2025-12-02 22:48:59 -06:00

2988 lines
114 KiB
C

#include "worldgen.h"
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>
#include <limits.h>
#define TRAIL_NODE_SPACING 1200.0
#define TRAIL_CELL_SIZE 16.0
#define TRAIL_MARGIN 96.0
#define TRAIL_WIDTH 5
typedef enum {
BIOME_WEST_KY_COALFIELDS = 0,
BIOME_EAST_KY_RIDGEBREAKS = 1,
BIOME_OLD_GROWTH_PLAINS = 2
} biome_id;
typedef struct {
int height;
int water_surface;
int has_water;
int basin_rim;
int basin_depth;
int is_local_basin;
biome_id biome;
} column_data;
typedef struct {
int x, y, z;
uint16_t id;
} placed_block;
typedef struct {
placed_block *items;
size_t count;
size_t cap;
} block_list;
typedef struct trail_segment {
int ax, az;
int bx, bz;
int count;
int *points; /* pairs of x,z world coordinates */
} trail_segment;
static int column_height(worldgen_ctx *ctx, int x, int z);
// ---------------------------------------------------------------------------
// RNG (deterministic, cheap)
// ---------------------------------------------------------------------------
typedef struct {
uint64_t state;
} rng_state;
static void rng_seed(rng_state *r, uint64_t seed) {
r->state = seed ? seed : 1;
}
static uint32_t rng_next_u32(rng_state *r) {
r->state = r->state * 6364136223846793005ULL + 1;
return (uint32_t)(r->state >> 32);
}
static double rng_next_f64(rng_state *r) {
return (rng_next_u32(r) + 1.0) / 4294967296.0;
}
static int rng_range_inclusive(rng_state *r, int min, int max) {
if (max <= min) return min;
int span = max - min + 1;
return min + (int)(rng_next_f64(r) * span);
}
struct tree_archetype;
typedef void (*tree_builder_fn)(worldgen_ctx *, int, int, int, int, rng_state *, block_list *, const struct tree_archetype *);
typedef enum {
TREE_SPECIES_OAK = 0,
TREE_SPECIES_BIRCH,
TREE_SPECIES_SPRUCE,
TREE_SPECIES_PINE,
TREE_SPECIES_WALNUT,
TREE_SPECIES_CYPRESS,
TREE_SPECIES_MAPLE,
TREE_SPECIES_COUNT
} tree_species;
typedef struct tree_archetype {
const char *name;
tree_species species;
int log_block;
int leaf_block;
int min_height;
int max_height;
int space_radius;
int canopy_extra;
tree_builder_fn builder;
} tree_archetype;
typedef struct {
tree_species species;
const char *label;
const int *variants;
size_t variant_count;
} tree_species_catalog;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
static inline double clamp01(double v) {
if (v < 0.0) return 0.0;
if (v > 1.0) return 1.0;
return v;
}
static inline int clamp_int(int v, int min_v, int max_v) {
if (v < min_v) return min_v;
if (v > max_v) return max_v;
return v;
}
static int ground_slope(worldgen_ctx *ctx, int x, int z);
static uint16_t select_surface_block(worldgen_ctx *ctx, const column_data *data, int world_x, int world_z);
static void generate_chunk_trails(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out);
static void generate_chunk_grass(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out);
static void generate_chunk_flowers(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out);
static void generate_chunk_cabins(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out);
static void ensure_trail_prepass(worldgen_ctx *ctx, int chunk_x, int chunk_z);
static void append_trail_segment(worldgen_ctx *ctx, int ax, int az, int bx, int bz, int *points, int count);
static void trail_node_position(worldgen_ctx *ctx, int node_x, int node_z, double spacing, double *out_x, double *out_z);
static void carve_trail_pad(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *out, column_data columns[CHUNK_SIZE][CHUNK_SIZE], int center_x, int center_z, int radius, int target_height);
static void carve_trail_span(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *out, column_data columns[CHUNK_SIZE][CHUNK_SIZE], int x0, int z0, int x1, int z1, int width);
static int build_trail_path(worldgen_ctx *ctx, double ax, double az, double bx, double bz, int **out_points, int *out_count);
static double old_growth_plains_mask(worldgen_ctx *ctx, int x, int z);
static double old_growth_grove_mask(worldgen_ctx *ctx, int x, int z);
static uint16_t generate_normal_ores(worldgen_ctx *ctx, int x, int y, int z, const column_data *col);
static void connect_cabin_to_trail(worldgen_ctx *ctx, int chunk_x, int chunk_z,
column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *chunk,
int door_x, int door_z, int door_side, int path_width);
static column_data get_column_data(worldgen_ctx *ctx, int x, int z);
static int generate_coal(worldgen_ctx *ctx, int x, int y, int z, int column_height, biome_id biome);
static uint32_t hash_coords(int x, int z, uint32_t seed) {
uint32_t h = (uint32_t)(x * 374761393 + z * 668265263) ^ seed;
h = (h ^ (h >> 13)) * 1274126177u;
return h ^ (h >> 16);
}
static uint32_t hash_coords3(int x, int y, int z, uint32_t seed) {
uint32_t h = (uint32_t)(x * 374761393 + y * 668265263 + z * 362827313) ^ seed;
h ^= (uint32_t)y * 0x9E3779B9u;
h = (h ^ (h >> 13)) * 1274126177u;
return h ^ (h >> 16);
}
static double land_value(worldgen_ctx *ctx, int x, int z) {
column_data col = get_column_data(ctx, x, z);
double value = 0.0;
double ore_bonus = 0.0;
for (int y = 1; y < col.height - 3; y += 6) {
if (generate_coal(ctx, x, y, z, col.height, col.biome)) {
ore_bonus += 0.08;
}
uint16_t ore = generate_normal_ores(ctx, x, y, z, &col);
if (ore != BLOCK_STONE) ore_bonus += 0.05;
}
value += ore_bonus;
if (col.biome == BIOME_OLD_GROWTH_PLAINS) value += 0.2;
if (col.biome == BIOME_WEST_KY_COALFIELDS) value += 0.1;
if (col.has_water && col.height >= col.water_surface - 2 && col.height <= col.water_surface + 2) {
value += 0.12;
}
int slope = ground_slope(ctx, x, z);
value -= clamp01((double)slope / 6.0) * 0.15;
double noise = simplex_noise2(&ctx->noise, x * 0.0015, z * 0.0015) * 0.5 + 0.5;
value += (noise - 0.5) * 0.08;
return value;
}
static void append_trail_segment(worldgen_ctx *ctx, int ax, int az, int bx, int bz, int *points, int count) {
if (ctx->trail_segment_count >= ctx->trail_segment_cap) {
size_t new_cap = ctx->trail_segment_cap ? ctx->trail_segment_cap * 2 : 32;
trail_segment *resized = (trail_segment *)realloc(ctx->trail_segments, new_cap * sizeof(trail_segment));
if (!resized) return;
ctx->trail_segments = resized;
ctx->trail_segment_cap = new_cap;
}
trail_segment *seg = &ctx->trail_segments[ctx->trail_segment_count];
memset(seg, 0, sizeof(*seg));
seg->ax = ax;
seg->az = az;
seg->bx = bx;
seg->bz = bz;
seg->points = points;
seg->count = count;
ctx->trail_segment_count++;
}
void worldgen_prepass(worldgen_ctx *ctx, int min_x, int max_x, int min_z, int max_z) {
if (!ctx) return;
if (ctx->prepass_done) {
if (min_x >= ctx->prepass_min_x && max_x <= ctx->prepass_max_x &&
min_z >= ctx->prepass_min_z && max_z <= ctx->prepass_max_z) {
return;
}
}
for (size_t i = 0; i < ctx->trail_segment_count; ++i) {
free(ctx->trail_segments[i].points);
}
free(ctx->trail_segments);
ctx->trail_segments = NULL;
ctx->trail_segment_count = 0;
ctx->trail_segment_cap = 0;
const int step = 96;
const int max_points = 32;
const double min_spacing = 160.0;
int cap = max_points;
int count = 0;
int *px = (int *)malloc((size_t)cap * sizeof(int));
int *pz = (int *)malloc((size_t)cap * sizeof(int));
double *pv = (double *)malloc((size_t)cap * sizeof(double));
if (!px || !pz || !pv) {
free(px); free(pz); free(pv);
return;
}
for (int z = min_z; z <= max_z; z += step) {
for (int x = min_x; x <= max_x; x += step) {
double val = land_value(ctx, x, z);
if (val < -0.05) continue;
int spaced = 1;
for (int i = 0; i < count; ++i) {
double dx = (double)(x - px[i]);
double dz = (double)(z - pz[i]);
if (dx * dx + dz * dz < min_spacing * min_spacing) {
spaced = 0;
break;
}
}
if (!spaced) continue;
if (count < cap) {
px[count] = x;
pz[count] = z;
pv[count] = val;
count++;
}
if (count >= max_points) break;
}
if (count >= max_points) break;
}
if (count < 2) {
px = (int *)realloc(px, 2 * sizeof(int));
pz = (int *)realloc(pz, 2 * sizeof(int));
pv = (double *)realloc(pv, 2 * sizeof(double));
if (!px || !pz || !pv) {
free(px); free(pz); free(pv);
return;
}
px[0] = (min_x + max_x) / 2;
pz[0] = (min_z + max_z) / 2;
pv[0] = 1.0;
px[1] = max_x - 16;
pz[1] = max_z - 16;
pv[1] = 0.8;
count = 2;
}
/* Select hubs: pick top valued, then farthest from existing hubs */
const int max_hubs = 2;
int hub_indices[2] = {0, 0};
int hub_count = 0;
int first = 0;
for (int i = 1; i < count; ++i) {
if (pv[i] > pv[first]) first = i;
}
hub_indices[hub_count++] = first;
for (int h = 1; h < max_hubs && hub_count < count; ++h) {
int best = -1;
double best_d2 = -1.0;
for (int i = 0; i < count; ++i) {
int already_hub = 0;
for (int k = 0; k < hub_count; ++k) if (hub_indices[k] == i) { already_hub = 1; break; }
if (already_hub) continue;
double min_d2 = DBL_MAX;
for (int k = 0; k < hub_count; ++k) {
double dx = (double)(px[i] - px[hub_indices[k]]);
double dz = (double)(pz[i] - pz[hub_indices[k]]);
double d2 = dx * dx + dz * dz;
if (d2 < min_d2) min_d2 = d2;
}
if (min_d2 > best_d2) {
best_d2 = min_d2;
best = i;
}
}
if (best >= 0) {
hub_indices[hub_count++] = best;
}
}
/* Connect hubs together (if two hubs exist) */
if (hub_count == 2) {
int *pts = NULL;
int path_count = 0;
if (build_trail_path(ctx, (double)px[hub_indices[0]], (double)pz[hub_indices[0]],
(double)px[hub_indices[1]], (double)pz[hub_indices[1]], &pts, &path_count) && pts && path_count >= 2) {
append_trail_segment(ctx, px[hub_indices[0]], pz[hub_indices[0]], px[hub_indices[1]], pz[hub_indices[1]], pts, path_count);
} else {
free(pts);
}
}
/* Connect every non-hub point to nearest hub */
for (int i = 0; i < count; ++i) {
int is_hub = 0;
for (int k = 0; k < hub_count; ++k) if (hub_indices[k] == i) { is_hub = 1; break; }
if (is_hub) continue;
int nearest_hub = hub_indices[0];
double best_d2 = DBL_MAX;
for (int k = 0; k < hub_count; ++k) {
double dx = (double)(px[i] - px[hub_indices[k]]);
double dz = (double)(pz[i] - pz[hub_indices[k]]);
double d2 = dx * dx + dz * dz;
if (d2 < best_d2) {
best_d2 = d2;
nearest_hub = hub_indices[k];
}
}
int *pts = NULL;
int path_count = 0;
if (build_trail_path(ctx, (double)px[i], (double)pz[i], (double)px[nearest_hub], (double)pz[nearest_hub], &pts, &path_count) && pts && path_count >= 2) {
append_trail_segment(ctx, px[i], pz[i], px[nearest_hub], pz[nearest_hub], pts, path_count);
} else {
free(pts);
}
}
free(px);
free(pz);
free(pv);
ctx->prepass_done = 1;
ctx->prepass_min_x = min_x;
ctx->prepass_max_x = max_x;
ctx->prepass_min_z = min_z;
ctx->prepass_max_z = max_z;
}
static void ensure_trail_prepass(worldgen_ctx *ctx, int chunk_x, int chunk_z) {
if (!ctx) return;
if (ctx->prepass_done) return;
int min_x = (chunk_x - 8) * CHUNK_SIZE;
int max_x = (chunk_x + 8) * CHUNK_SIZE + (CHUNK_SIZE - 1);
int min_z = (chunk_z - 8) * CHUNK_SIZE;
int max_z = (chunk_z + 8) * CHUNK_SIZE + (CHUNK_SIZE - 1);
worldgen_prepass(ctx, min_x, max_x, min_z, max_z);
}
void worldgen_free_trails(worldgen_ctx *ctx) {
if (!ctx || !ctx->trail_segments) return;
for (size_t i = 0; i < ctx->trail_segment_count; ++i) {
free(ctx->trail_segments[i].points);
}
free(ctx->trail_segments);
ctx->trail_segments = NULL;
ctx->trail_segment_count = 0;
ctx->trail_segment_cap = 0;
}
static void set_block_with_height(chunk_data *chunk, int local_x, int local_z, int y, uint16_t id) {
if (local_x < 0 || local_x >= CHUNK_SIZE || local_z < 0 || local_z >= CHUNK_SIZE) return;
if (y < 0 || y >= CHUNK_HEIGHT) return;
chunk->blocks[y][local_x][local_z] = id;
if (id != BLOCK_AIR) {
uint16_t current = chunk->heightmap[local_x][local_z];
if ((uint16_t)y > current) {
chunk->heightmap[local_x][local_z] = (uint16_t)y;
}
}
}
static double worley_distance(int x, int z, double scale, uint32_t seed) {
double px = x * scale;
double pz = z * scale;
int cell_x = (int)floor(px);
int cell_z = (int)floor(pz);
double min_dist = 9999.0;
for (int dx = -1; dx <= 1; ++dx) {
for (int dz = -1; dz <= 1; ++dz) {
int cx = cell_x + dx;
int cz = cell_z + dz;
uint32_t h = hash_coords(cx, cz, seed);
double rand_x = ((h & 0xFFFF) / 65535.0);
double rand_z = (((h >> 16) & 0xFFFF) / 65535.0);
double feature_x = cx + rand_x;
double feature_z = cz + rand_z;
double dist = sqrt((feature_x - px) * (feature_x - px) + (feature_z - pz) * (feature_z - pz));
if (dist < min_dist) min_dist = dist;
}
}
return min_dist;
}
static double ore_depth_weight(int y, int top, int bottom) {
if (y > top || y < bottom) return 0.0;
if (top <= bottom) return 1.0;
double span = (double)(top - bottom);
if (span <= 0.0) return 1.0;
double weight = (double)(top - y) / span;
if (weight < 0.0) weight = 0.0;
if (weight > 1.0) weight = 1.0;
return weight;
}
static double ore_cluster_field(worldgen_ctx *ctx, int x, int y, int z, double scale, double offset) {
double n = simplex_noise3(&ctx->noise,
x * scale + offset,
y * scale * 0.9 - offset * 0.37,
z * scale - offset * 0.21);
return n * 0.5 + 0.5;
}
static double old_growth_plains_mask(worldgen_ctx *ctx, int x, int z) {
(void)ctx;
double coarse = worley_distance(x + 21000, z - 21000, 0.00065, 0x9E3779B9u);
double patch = clamp01(1.15 - coarse * 1.05);
double ridge = worley_distance(x - 18000, z + 18000, 0.00035, 0xC001D00Du);
double hollow = clamp01((ridge - 0.35) * 1.5);
double detail = simplex_noise2(&ctx->noise, x * 0.00045, z * 0.00045) * 0.5 + 0.5;
double mask = patch * (0.7 + detail * 0.3) * (1.0 - hollow * 0.6);
return clamp01(mask);
}
static double region_blend(worldgen_ctx *ctx, int x, int z) {
(void)ctx;
double primary = worley_distance(x, z, 0.0012, 0x87654321u);
double normalized = clamp01(1.2 - primary * 0.9);
double detail = worley_distance(x + 40000, z - 35000, 0.0023, 0x13579BDFu);
detail = clamp01(detail * 1.1);
double blend = normalized * 0.75 + detail * 0.25;
return clamp01(blend);
}
static int local_relief(worldgen_ctx *ctx, int x, int z, int center_height) {
const int offsets[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int max_delta = 0;
for (int i = 0; i < 4; ++i) {
int nx = x + offsets[i][0];
int nz = z + offsets[i][1];
int neighbor = column_height(ctx, nx, nz);
int delta = abs(neighbor - center_height);
if (delta > max_delta) max_delta = delta;
}
return max_delta;
}
static biome_id classify_biome(worldgen_ctx *ctx, int x, int z, int column_height) {
double old_growth = old_growth_plains_mask(ctx, x, z);
if (old_growth > 0.6) {
return BIOME_OLD_GROWTH_PLAINS;
}
double blend = region_blend(ctx, x, z);
int slope = local_relief(ctx, x, z, column_height);
double slope_score = clamp01((double)slope / 10.0);
double relief = clamp01((double)(column_height - ctx->sea_level) / 45.0);
double jitter = simplex_noise2(&ctx->noise, (x - 17000) * 0.001, (z + 17000) * 0.001) * 0.1;
double decision = blend * 0.7 + slope_score * 0.2 + relief * 0.15 + jitter;
if (decision > 0.55) {
return BIOME_EAST_KY_RIDGEBREAKS;
}
return BIOME_WEST_KY_COALFIELDS;
}
static void block_list_init(block_list *list) {
list->items = NULL;
list->count = 0;
list->cap = 0;
}
static void block_list_free(block_list *list) {
free(list->items);
list->items = NULL;
list->count = list->cap = 0;
}
static void block_list_push(block_list *list, int x, int y, int z, uint16_t id) {
if (list->count >= list->cap) {
size_t new_cap = list->cap ? list->cap * 2 : 64;
placed_block *new_items = (placed_block *)realloc(list->items, new_cap * sizeof(placed_block));
if (!new_items) {
return;
}
list->items = new_items;
list->cap = new_cap;
}
list->items[list->count].x = x;
list->items[list->count].y = y;
list->items[list->count].z = z;
list->items[list->count].id = id;
list->count += 1;
}
// ---------------------------------------------------------------------------
// Core worldgen functions
// ---------------------------------------------------------------------------
static int column_height(worldgen_ctx *ctx, int x, int z) {
double region = region_blend(ctx, x, z);
double old_growth = old_growth_plains_mask(ctx, x, z);
double warp1_x = x + simplex_noise2(&ctx->noise, x * 0.001, z * 0.001) * 50.0;
double warp1_z = z + simplex_noise2(&ctx->noise, (x + 1000) * 0.001, (z + 1000) * 0.001) * 50.0;
double warp2_x = warp1_x + simplex_noise2(&ctx->noise, warp1_x * 0.01, warp1_z * 0.01) * 10.0;
double warp2_z = warp1_z + simplex_noise2(&ctx->noise, (warp1_x + 500) * 0.01, (warp1_z + 500) * 0.01) * 10.0;
double mountain_mask = simplex_noise2(&ctx->noise, warp2_x * 0.00035, warp2_z * 0.00035);
double base_mountain = clamp01((mountain_mask + 0.25) * 0.6);
double mountain_factor = clamp01(base_mountain * 0.85 + region * 0.35);
double lowland = ctx->sea_level - 4;
lowland += simplex_noise2(&ctx->noise, warp2_x * 0.0004, warp2_z * 0.0004) * 52.0 * (0.45 + 0.25 * region);
lowland += simplex_noise2(&ctx->noise, warp2_x * 0.0022, warp2_z * 0.0022) * 24.0 * (0.38 + 0.2 * region);
lowland += simplex_noise2(&ctx->noise, warp2_x * 0.006, warp2_z * 0.006) * 10.0 * (0.34 + 0.2 * region);
lowland += simplex_noise2(&ctx->noise, warp2_x * 0.012, warp2_z * 0.012) * 4.0 * (0.3 + 0.15 * region);
double hummock = simplex_noise2(&ctx->noise, (warp2_x + 32000) * 0.02, (warp2_z - 32000) * 0.02);
lowland += hummock * hummock * 3.5;
double highland = ctx->sea_level - 10;
highland += simplex_noise2(&ctx->noise, warp2_x * 0.0005, warp2_z * 0.0005) * 120.0 * (0.65 + 0.35 * mountain_factor);
highland += simplex_noise2(&ctx->noise, warp2_x * 0.002, warp2_z * 0.002) * 55.0 * (0.5 + 0.5 * mountain_factor);
highland += simplex_noise2(&ctx->noise, warp2_x * 0.005, warp2_z * 0.005) * 32.0 * (0.4 + 0.6 * mountain_factor);
highland += simplex_noise2(&ctx->noise, warp2_x * 0.01, warp2_z * 0.01) * 20.0 * (0.35 + 0.65 * mountain_factor);
double mix_base = region * 0.35 + base_mountain * 0.55;
double mix = clamp01(mix_base * 0.65 + 0.15);
double height = lowland * (1.0 - mix) + highland * mix;
double flat_noise = simplex_noise2(&ctx->noise, warp2_x * 0.0015, warp2_z * 0.0015) * 0.5 + 0.5;
double flat_bias = (mix < 0.45 ? (0.45 - mix) * 1.6 : 0.0) * flat_noise;
if (flat_bias > 0.0) {
double target = ctx->sea_level - 2 + simplex_noise2(&ctx->noise, warp2_x * 0.004, warp2_z * 0.004) * 4.0;
double blend = 0.3 * flat_bias;
height = height * (1.0 - blend) + target * blend;
}
double detail_scale = 0.04 + 0.35 * mountain_factor;
double detail_weight = detail_scale * (0.35 + 0.65 * mountain_factor);
double region_bias = 0.7 + 0.3 * clamp01(region);
double plains_detail_quiet = clamp01(1.0 - mix * 1.2);
double adjusted_weight = detail_weight * region_bias * (1.0 - plains_detail_quiet * 0.8);
height += simplex_noise2(&ctx->noise, warp2_x * 0.02, warp2_z * 0.02) * 5.0 * adjusted_weight;
height += simplex_noise2(&ctx->noise, warp2_x * 0.05, warp2_z * 0.05) * 2.0 * adjusted_weight;
height += simplex_noise2(&ctx->noise, warp2_x * 0.1, warp2_z * 0.1) * 1.0 * adjusted_weight;
height += simplex_noise3(&ctx->noise, warp2_x * 0.015, warp2_z * 0.015, region * 7.0) * 1.5;
double flats = clamp01((old_growth - 0.25) / 0.75);
if (flats > 0.0) {
double plains_target = ctx->sea_level - 2 + simplex_noise2(&ctx->noise, (x - 22000) * 0.002, (z + 22000) * 0.002) * 5.0;
plains_target += simplex_noise2(&ctx->noise, (x + 33000) * 0.01, (z - 33000) * 0.01) * 1.5;
height = height * (1.0 - flats) + plains_target * flats;
}
return (int)height;
}
static column_data get_column_data(worldgen_ctx *ctx, int x, int z) {
column_data data;
data.height = column_height(ctx, x, z);
int rim = data.height;
int has_rim = 0;
for (int dx = -4; dx <= 4; ++dx) {
for (int dz = -4; dz <= 4; ++dz) {
if (dx == 0 && dz == 0) continue;
int neighbor = column_height(ctx, x + dx, z + dz);
if (!has_rim || neighbor < rim) {
rim = neighbor;
has_rim = 1;
}
}
}
if (has_rim && rim - data.height >= 4) {
data.has_water = 1;
data.water_surface = rim;
data.basin_rim = rim;
data.basin_depth = rim - data.height;
data.is_local_basin = 1;
} else if (data.height < ctx->sea_level) {
data.has_water = 1;
data.water_surface = ctx->sea_level;
data.basin_rim = ctx->sea_level;
data.basin_depth = ctx->sea_level - data.height;
data.is_local_basin = 0;
} else {
data.has_water = 0;
data.water_surface = 0;
data.basin_rim = data.height;
data.basin_depth = 0;
data.is_local_basin = 0;
}
if (data.is_local_basin && data.basin_depth > 5) {
double depth = (double)data.basin_depth;
double norm = clamp01(depth / 18.0);
double carve = pow(norm, 1.2) * (2.5 + depth * 0.25);
int carve_int = (int)(carve);
if (carve_int > 0) {
data.height -= carve_int;
if (data.height < 1) data.height = 1;
data.basin_depth = data.basin_rim - data.height;
}
}
data.biome = classify_biome(ctx, x, z, data.height);
return data;
}
// ---------------------------------------------------------------------------
// Ore generation (coal seams)
// ---------------------------------------------------------------------------
typedef enum {
COAL_ZONE_WEST,
COAL_ZONE_FOOTHILL,
COAL_ZONE_EAST
} coal_zone;
static coal_zone classify_coal_zone(worldgen_ctx *ctx, int column_height, biome_id biome) {
int relief = column_height - ctx->sea_level;
if (biome == BIOME_WEST_KY_COALFIELDS || biome == BIOME_OLD_GROWTH_PLAINS) {
if (relief > 24) return COAL_ZONE_FOOTHILL;
return COAL_ZONE_WEST;
}
if (relief > 36) return COAL_ZONE_EAST;
return COAL_ZONE_FOOTHILL;
}
static int coal_min_y_for_zone(coal_zone zone) {
switch (zone) {
case COAL_ZONE_WEST:
return 10; // keep western strip seams above the deep stone layers
case COAL_ZONE_FOOTHILL:
return 6;
case COAL_ZONE_EAST:
default:
return 2; // mountains can dip close to bedrock
}
}
static int check_point_in_seam(double y, double center, double half_thickness) {
return fabs(y - center) <= half_thickness;
}
static double coal_seam_offset(worldgen_ctx *ctx, double x, double y, double z, double scale, double amplitude) {
return simplex_noise3(&ctx->noise, x * scale, y * scale, z * scale) * amplitude;
}
static double coal_thickness_variation(worldgen_ctx *ctx, double x, double z, double scale, double amplitude) {
return simplex_noise2(&ctx->noise, x * scale, z * scale) * amplitude;
}
static int coal_continuity(worldgen_ctx *ctx, double x, double y, double z, double threshold) {
double n = simplex_noise3(&ctx->noise, x * 0.05, y * 0.05, z * 0.05);
return n > threshold;
}
static int seam_presence(worldgen_ctx *ctx, double x, double z, double scale, double threshold, double offset) {
double n = simplex_noise2(&ctx->noise, x * scale + offset, z * scale - offset);
return n > threshold;
}
static double coal_mountain_bias(worldgen_ctx *ctx, int column_height) {
double rel = (double)(column_height - (ctx->sea_level + 20));
return clamp01(rel / 80.0);
}
static int seam_presence_biased(worldgen_ctx *ctx, double x, double z, double scale, double threshold, double offset, double bias) {
double adjusted = threshold + (1.0 - clamp01(bias)) * 0.4;
if (adjusted > 0.98) adjusted = 0.98;
return seam_presence(ctx, x, z, scale, adjusted, offset);
}
static int check_seam_generic(worldgen_ctx *ctx, double x, double y, double z, double center, double thickness,
double undulation_scale, double undulation_amp, double thickness_scale,
double thickness_amp, double continuity_threshold) {
double offset = coal_seam_offset(ctx, x, y, z, undulation_scale, undulation_amp);
double adjusted_center = center + offset;
double thickness_var = coal_thickness_variation(ctx, x, z, thickness_scale, thickness_amp);
double adjusted_thickness = thickness + thickness_var;
double half_thickness = adjusted_thickness / 2.0;
if (check_point_in_seam(y, adjusted_center, half_thickness)) {
if (coal_continuity(ctx, x, y, z, continuity_threshold)) {
return 1;
}
}
return 0;
}
static int check_flat_ceiling_seam(worldgen_ctx *ctx, double x, double y, double z, double column_height,
double ceiling_depth, double thickness, double continuity_threshold) {
double seam_ceiling = column_height - ceiling_depth;
double seam_floor = seam_ceiling - thickness;
if (y < seam_floor || y > seam_ceiling) {
return 0;
}
return coal_continuity(ctx, x, y, z, continuity_threshold);
}
static int check_terrain_relative_seam(worldgen_ctx *ctx, double x, double y, double z, double column_height,
double depth_below_surface, double thickness,
double thickness_scale, double thickness_amp, double continuity_threshold) {
double seam_center = column_height - depth_below_surface;
double thickness_var = coal_thickness_variation(ctx, x, z, thickness_scale, thickness_amp);
double adjusted_thickness = thickness + thickness_var;
double half_thickness = adjusted_thickness / 2.0;
if (check_point_in_seam(y, seam_center, half_thickness)) {
if (coal_continuity(ctx, x, y, z, continuity_threshold)) {
return 1;
}
}
return 0;
}
static int generate_coal(worldgen_ctx *ctx, int x, int y, int z, int column_height, biome_id biome) {
coal_zone zone = classify_coal_zone(ctx, column_height, biome);
if (y < coal_min_y_for_zone(zone)) {
return 0;
}
double mountain_bias = coal_mountain_bias(ctx, column_height);
double seam_bias = clamp01(0.05 + mountain_bias * 0.9);
if (zone == COAL_ZONE_EAST) {
seam_bias = clamp01(seam_bias + 0.3);
} else if (zone == COAL_ZONE_FOOTHILL) {
seam_bias = clamp01(seam_bias + 0.15);
}
// Room-and-pillar seams for foothills/mountains (keep deep layers intact in lowlands).
if (zone != COAL_ZONE_WEST) {
if (seam_presence_biased(ctx, x, z, 0.0012, 0.0, 7.0, seam_bias) &&
check_seam_generic(ctx, x, y, z, 12, 5, 0.02, 2.0, 0.03, 1.0, -0.7))
return 1;
}
if (seam_presence_biased(ctx, x, z, 0.0009, -0.1, 11.0, seam_bias) &&
check_seam_generic(ctx, x, y, z, 25, 3, 0.015, 1.5, 0.04, 0.5, -0.6))
return 1;
if (seam_presence_biased(ctx, x, z, 0.0011, -0.05, -7.0, seam_bias) &&
check_seam_generic(ctx, x, y, z, 32, 4, 0.02, 2.0, 0.03, 1.0, -0.7))
return 1;
if (seam_presence_biased(ctx, x, z, 0.0015, 0.05, 3.0, seam_bias) &&
check_seam_generic(ctx, x, y, z, 45, 2, 0.015, 1.5, 0.04, 0.5, -0.6))
return 1;
if (seam_presence_biased(ctx, x, z, 0.002, 0.1, 17.0, seam_bias) &&
check_seam_generic(ctx, x, y, z, 85, 3, 0.02, 2.0, 0.03, 1.0, -0.7))
return 1;
// Mountain strip seams: shallow, dense coal layers for high relief areas.
if (zone == COAL_ZONE_EAST) {
if (seam_presence_biased(ctx, x, z, 0.0014, -0.05, 42.0, seam_bias) &&
check_terrain_relative_seam(ctx, x, y, z, column_height, 4.0, 4.0, 0.04, 0.9, -0.4))
return 1;
}
// Surface-adjacent seams get fatter in the western lowlands (strip mining).
double strip_thickness = (zone == COAL_ZONE_WEST) ? 2.5 : 1.3;
double strip_bias = clamp01(seam_bias * 0.6 + 0.1);
if (seam_presence_biased(ctx, x, z, 0.0007, 0.2, 28.0, strip_bias)) {
if (check_flat_ceiling_seam(ctx, x, y, z, column_height, 5, strip_thickness, -0.25)) return 1;
}
double relative_thickness = (zone == COAL_ZONE_WEST) ? 2.6 : 1.8;
double relative_depth = (zone == COAL_ZONE_EAST) ? 11.0 : 8.0;
if (seam_presence_biased(ctx, x, z, 0.001, -0.05, 3.5, seam_bias) &&
check_terrain_relative_seam(ctx, x, y, z, column_height, relative_depth, relative_thickness, 0.03, 0.8, -0.62))
return 1;
// Mountain seams can drop deeper for mountaintop-removal vibes.
if (zone == COAL_ZONE_EAST) {
if (seam_presence_biased(ctx, x, z, 0.0013, -0.15, -19.0, clamp01(seam_bias + 0.2)) &&
check_terrain_relative_seam(ctx, x, y, z, column_height, 18, 3.5, 0.025, 0.6, -0.55))
return 1;
if (seam_presence_biased(ctx, x, z, 0.0018, 0.05, 9.0, clamp01(seam_bias + 0.2)) &&
check_seam_generic(ctx, x, y, z, 8, 4, 0.02, 1.8, 0.03, 1.0, -0.6))
return 1;
}
return 0;
}
// ---------------------------------------------------------------------------
// Terrain block generation
// ---------------------------------------------------------------------------
static uint16_t select_surface_block(worldgen_ctx *ctx, const column_data *data, int world_x, int world_z) {
if (data->has_water && data->water_surface >= data->height) {
int slope = ground_slope(ctx, world_x, world_z);
if (slope <= 1) return BLOCK_SAND;
if (slope <= 3) return BLOCK_GRAVEL;
return BLOCK_STONE;
}
int snow_line = ctx->snow_line;
int snow_fade = 6;
if (data->height >= snow_line) {
return BLOCK_SNOW;
}
if (data->height >= snow_line - snow_fade) {
double t = (double)(data->height - (snow_line - snow_fade)) / (double)snow_fade;
double noise = simplex_noise2(&ctx->noise, world_x * 0.02, world_z * 0.02) * 0.5 + 0.5;
if (noise < t) return BLOCK_SNOW;
}
return BLOCK_GRASS;
}
static int sample_block(worldgen_ctx *ctx, int x, int y, int z) {
if (y == 0) return BLOCK_BEDROCK;
column_data data = get_column_data(ctx, x, z);
if (y < data.height - 3) {
if (generate_coal(ctx, x, y, z, data.height, data.biome)) {
return BLOCK_COAL;
}
return (int)generate_normal_ores(ctx, x, y, z, &data);
}
if (y < data.height) return BLOCK_DIRT;
if (y == data.height) {
return select_surface_block(ctx, &data, x, z);
}
if (data.has_water && y <= data.water_surface && y > data.height) return BLOCK_WATER;
return BLOCK_AIR;
}
static int ground_slope(worldgen_ctx *ctx, int x, int z) {
int center = column_height(ctx, x, z);
int max_delta = 0;
const int offsets[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int i = 0; i < 4; ++i) {
int nx = x + offsets[i][0];
int nz = z + offsets[i][1];
int neighbor = column_height(ctx, nx, nz);
int delta = abs(neighbor - center);
if (delta > max_delta) max_delta = delta;
}
return max_delta;
}
static double tree_density_mask(worldgen_ctx *ctx, int x, int z) {
double forest = simplex_noise2(&ctx->noise, (x + 3000) * 0.01, (z - 3000) * 0.01);
double moisture = simplex_noise2(&ctx->noise, (x - 5000) * 0.02, (z + 5000) * 0.02);
return (forest * 0.6 + moisture * 0.4 + 1.0) * 0.5;
}
static double old_growth_grove_mask(worldgen_ctx *ctx, int x, int z) {
double cell = worley_distance(x + 8000, z - 8000, 0.0014, 0x8BADF00Du);
double cluster = clamp01(1.25 - cell * 1.1);
double noise = simplex_noise2(&ctx->noise, (x - 12000) * 0.003, (z + 12000) * 0.003) * 0.5 + 0.5;
return clamp01(cluster * 0.7 + noise * 0.3);
}
static int can_place_tree(worldgen_ctx *ctx, int x, int y, int z, int trunk_height, int radius, int crown_extra) {
int ground_block = sample_block(ctx, x, y - 1, z);
if (ground_block != BLOCK_GRASS && ground_block != BLOCK_SNOW && ground_block != BLOCK_DIRT) return 0;
int top = y + trunk_height + crown_extra;
if (top >= CHUNK_HEIGHT) return 0;
for (int cy = y; cy <= top; ++cy) {
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
if (cy < y && (dx != 0 || dz != 0)) continue;
int block = sample_block(ctx, x + dx, cy, z + dz);
if (block != BLOCK_AIR && block != BLOCK_SNOW) {
return 0;
}
}
}
}
return 1;
}
static void place_log_column(int x, int y, int z, int height, int log_block, block_list *out) {
for (int dy = 0; dy < height; ++dy) {
block_list_push(out, x, y + dy, z, (uint16_t)log_block);
}
}
static void extend_trunk_to(int x, int y, int z, int current_height, int target_height, int log_block, block_list *out) {
for (int dy = current_height; dy < target_height; ++dy) {
block_list_push(out, x, y + dy, z, (uint16_t)log_block);
}
}
static void reinforce_trunk(int x, int y, int z, int top_y, int log_block, block_list *out) {
if (top_y < y) return;
if (top_y >= CHUNK_HEIGHT) top_y = CHUNK_HEIGHT - 1;
for (int py = y; py <= top_y; ++py) {
block_list_push(out, x, py, z, (uint16_t)log_block);
}
}
static void place_leaf_circle(int cx, int cy, int cz, int radius, int leaf_block, rng_state *rng, double hole_prob, block_list *out) {
int r2 = radius * radius;
for (int dx = -radius; dx <= radius; ++dx) {
for (int dz = -radius; dz <= radius; ++dz) {
if (dx * dx + dz * dz > r2) continue;
if (hole_prob > 0.0 && rng_next_f64(rng) < hole_prob) continue;
block_list_push(out, cx + dx, cy, cz + dz, (uint16_t)leaf_block);
}
}
}
static void place_leaf_blob(int cx, int cy, int cz, int radius, int height, int leaf_block, rng_state *rng, block_list *out) {
for (int dy = -height; dy <= height; ++dy) {
int level_radius = radius - abs(dy) / 2;
if (level_radius < 1) level_radius = 1;
place_leaf_circle(cx, cy + dy, cz, level_radius, leaf_block, rng, 0.2, out);
}
}
static void place_branch_span(int x, int y, int z, int dx, int dz, int length, int lift_step, int log_block, int leaf_block, rng_state *rng, block_list *out) {
int cx = x;
int cy = y;
int cz = z;
for (int step = 1; step <= length; ++step) {
cx += dx;
cz += dz;
if (lift_step > 0 && step % lift_step == 0) {
cy += 1;
}
block_list_push(out, cx, cy, cz, (uint16_t)log_block);
if (leaf_block >= 0) {
int radius = (step == length) ? 2 : 1;
double hole = 0.2 + 0.1 * step;
place_leaf_circle(cx, cy, cz, radius, leaf_block, rng, hole, out);
}
}
}
static void clear_snow_block(worldgen_ctx *ctx, int x, int y, int z, block_list *out) {
int block = sample_block(ctx, x, y, z);
if (block == BLOCK_SNOW) {
block_list_push(out, x, y, z, BLOCK_AIR);
}
}
static void build_oak_round(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height - 1;
if (trunk_height < 4) trunk_height = 4;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int crown_start = y + trunk_height - 2;
int crown_top = y + trunk_height + 3;
int leaf_top = crown_top;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int cy = crown_start; cy <= crown_top; ++cy) {
int rel = cy - crown_start;
int radius = 2;
if (rel == 0) radius = 1;
else if (rel >= 3) radius = 2 + (rel == 3);
if (rel >= 4) radius = 1;
place_leaf_circle(x, cy, z, radius + rng_range_inclusive(rng, 0, 1), arch->leaf_block, rng, 0.15, out);
}
}
static void build_oak_sprawl(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height - 2;
if (trunk_height < 5) trunk_height = 5;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int canopy_base = y + trunk_height - 1;
int leaf_top = canopy_base + 5;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int layer = 0; layer < 5; ++layer) {
int cy = canopy_base + layer;
int radius = 3 + (layer <= 1 ? 1 : -1);
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, 0.25, out);
if (layer % 2 == 0) {
place_leaf_circle(x, cy, z, radius + 1, arch->leaf_block, rng, 0.4, out);
}
}
place_leaf_circle(x, canopy_base + 5, z, 1, arch->leaf_block, rng, 0.0, out);
}
static void build_oak_columnar(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + rng_range_inclusive(rng, 0, 2);
if (trunk_height < 6) trunk_height = 6;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int canopy_start = y + trunk_height - 3;
int canopy_top = canopy_start + 4;
int leaf_top = canopy_top + 1;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int cy = canopy_start; cy <= canopy_top; ++cy) {
int rel = cy - canopy_start;
int radius = 2;
if (rel == 0) radius = 3;
if (rel >= 3) radius = 1;
double holes = (rel == 1) ? 0.2 : 0.3;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, holes, out);
}
const int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
int branch_y = canopy_start - 1;
for (int i = 0; i < 4; ++i) {
if (rng_next_f64(rng) > 0.75) continue;
int bx = x + dirs[i][0];
int bz = z + dirs[i][1];
block_list_push(out, bx, branch_y, bz, (uint16_t)arch->log_block);
place_leaf_circle(bx, branch_y, bz, 2, arch->leaf_block, rng, 0.35, out);
}
place_leaf_circle(x, canopy_top + 1, z, 1, arch->leaf_block, rng, 0.0, out);
}
static void build_birch_slender(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height < 5 ? 5 : height;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int crown_start = y + trunk_height - 2;
int leaf_top = crown_start + 4;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int layer = 0; layer < 5; ++layer) {
int cy = crown_start + layer;
int radius = 1 + (layer >= 2 ? 1 : 0);
if (layer == 4) radius = 1;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, 0.15 + layer * 0.05, out);
}
int mid_start = y + trunk_height / 2;
for (int cy = mid_start; cy < crown_start; cy += 2) {
int small_radius = (cy % 4 == 0) ? 2 : 1;
place_leaf_circle(x, cy, z, small_radius, arch->leaf_block, rng, 0.35, out);
}
}
static void build_birch_brush(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height - 1;
if (trunk_height < 5) trunk_height = 5;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int leaf_base = y + trunk_height - 3;
int leaf_top = leaf_base + 5;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int cy = leaf_base; cy <= leaf_top; ++cy) {
int rel = cy - leaf_base;
int radius = 2 + (rel <= 1 ? 1 : 0);
if (rel >= 4) radius = 1;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, 0.2, out);
}
for (int cy = y + trunk_height / 2; cy < leaf_base; cy += 2) {
if (rng_next_f64(rng) < 0.5) {
place_leaf_circle(x, cy, z, 1, arch->leaf_block, rng, 0.15, out);
}
}
}
static void build_birch_fan(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 1;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int canopy_start = y + trunk_height - 4;
int canopy_top = canopy_start + 5;
extend_trunk_to(x, y, z, trunk_height, canopy_top - y, arch->log_block, out);
for (int cy = canopy_start; cy <= canopy_top; ++cy) {
int rel = cy - canopy_start;
int radius = 3 - (rel / 2);
if (radius < 1) radius = 1;
double hole = 0.25 + rel * 0.05;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, hole, out);
}
const int diag[4][2] = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
for (int i = 0; i < 4; ++i) {
int bx = x + diag[i][0];
int bz = z + diag[i][1];
block_list_push(out, bx, canopy_start - 1, bz, (uint16_t)arch->leaf_block);
}
}
static void build_pine_spire(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 2;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int leaf_start = y + trunk_height / 4;
int leaf_top = y + trunk_height + 3;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int cy = leaf_start; cy <= leaf_top; ++cy) {
double t = (double)(cy - leaf_start) / (double)(leaf_top - leaf_start + 1);
int radius = (int)(3 - t * 3.5);
if (radius < 1) radius = 1;
double holes = 0.1 + t * 0.3;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, holes, out);
}
place_leaf_circle(x, leaf_top + 1, z, 1, arch->leaf_block, rng, 0.0, out);
}
static void build_pine_crown(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 1;
if (trunk_height < 7) trunk_height = 7;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int leaf_base = y + trunk_height - 4;
int leaf_top = y + trunk_height + 2;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int cy = leaf_base; cy <= leaf_top; ++cy) {
int rel = cy - leaf_base;
int radius = 3 - rel / 2;
if (radius < 1) radius = 1;
double hole = 0.1 + rel * 0.05;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, hole, out);
if (rel == 1 || rel == 2) {
place_leaf_circle(x, cy - 1, z, radius + 1, arch->leaf_block, rng, 0.35, out);
}
}
reinforce_trunk(x, y, z, leaf_top, arch->log_block, out);
}
static void build_spruce_tiers(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 3;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int leaf_top = y + trunk_height + 1;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
int tier_count = 4 + height / 3;
int tier_spacing = 2;
for (int t = 0; t < tier_count; ++t) {
int cy = y + trunk_height - t * tier_spacing;
if (cy < y + 2) break;
int radius = 1 + (tier_count - t);
if (radius > 4) radius = 4;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, 0.15, out);
place_leaf_circle(x, cy - 1, z, radius - 1, arch->leaf_block, rng, 0.2, out);
}
place_leaf_circle(x, y + trunk_height + 1, z, 1, arch->leaf_block, rng, 0.0, out);
reinforce_trunk(x, y, z, leaf_top, arch->log_block, out);
}
static void build_spruce_cone(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 4;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int leaf_start = y + trunk_height / 3;
int leaf_top = y + trunk_height + 2;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int cy = leaf_start; cy <= leaf_top; ++cy) {
double t = (double)(cy - leaf_start) / (double)(leaf_top - leaf_start + 1);
int radius = 4 - (int)round(t * 3.5);
if (radius < 1) radius = 1;
double hole = 0.1 + t * 0.25;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, hole, out);
if (t > 0.35 && rng_next_f64(rng) < 0.5) {
place_leaf_circle(x, cy - 1, z, radius + 1, arch->leaf_block, rng, 0.4, out);
}
}
reinforce_trunk(x, y, z, leaf_top, arch->log_block, out);
place_leaf_circle(x, leaf_top + 1, z, 1, arch->leaf_block, rng, 0.0, out);
}
static void build_spruce_spread(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 1;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int tier_base = y + trunk_height - 2;
int leaf_top = y + trunk_height + 1;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int tier = 0; tier < 4; ++tier) {
int cy = tier_base - tier * 2;
if (cy < y + 2) break;
int radius = 3 - tier / 2;
if (radius < 1) radius = 1;
place_leaf_circle(x, cy, z, radius + 1, arch->leaf_block, rng, 0.2, out);
place_leaf_circle(x, cy - 1, z, radius, arch->leaf_block, rng, 0.3, out);
}
reinforce_trunk(x, y, z, leaf_top, arch->log_block, out);
place_leaf_circle(x, leaf_top + 1, z, 1, arch->leaf_block, rng, 0.0, out);
}
static void build_walnut_table(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height - 1;
if (trunk_height < 4) trunk_height = 4;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int canopy_center = y + trunk_height;
int leaf_top = canopy_center + 3;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
place_leaf_blob(x, canopy_center, z, 4, 2, arch->leaf_block, rng, out);
place_leaf_circle(x, canopy_center + 3, z, 2, arch->leaf_block, rng, 0.15, out);
}
static void build_walnut_vase(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int canopy_center = y + trunk_height - 1;
int leaf_top = canopy_center + 4;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
place_leaf_blob(x, canopy_center, z, 3, 3, arch->leaf_block, rng, out);
place_leaf_circle(x, canopy_center + 4, z, 2, arch->leaf_block, rng, 0.2, out);
for (int i = 0; i < 4; ++i) {
if (rng_next_f64(rng) < 0.5) {
int offset = (i % 2 == 0) ? 2 : -2;
block_list_push(out, x + offset, canopy_center - 1, z, (uint16_t)arch->log_block);
place_leaf_circle(x + offset, canopy_center - 1, z, 2, arch->leaf_block, rng, 0.35, out);
}
}
}
static void build_cypress_column(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 4;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int leaf_start = y + trunk_height / 2;
int leaf_top = y + trunk_height + 2;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int cy = leaf_start; cy <= leaf_top; ++cy) {
int radius = (cy > y + trunk_height) ? 1 : 1 + ((cy - leaf_start) % 2 == 0);
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, 0.25, out);
}
}
static void build_cypress_fan(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 2;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int leaf_start = y + trunk_height / 2;
int leaf_top = y + trunk_height + 3;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int cy = leaf_start; cy <= leaf_top; ++cy) {
int rel = cy - leaf_start;
int radius = (rel % 2 == 0) ? 2 : 1;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, 0.3, out);
}
}
static void build_maple_tangle(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height - 1;
if (trunk_height < 5) trunk_height = 5;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int branch_levels = 3;
int leaf_top = y + trunk_height + 2;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int i = 0; i < branch_levels; ++i) {
int level_y = y + trunk_height - (i * 2);
int radius = 2 + i;
place_leaf_circle(x, level_y, z, radius, arch->leaf_block, rng, 0.25, out);
int branches = 4 + i;
for (int b = 0; b < branches; ++b) {
double angle = (double)b / branches * 6.28318 + rng_next_f64(rng) * 0.2;
int bx = x + (int)round(cos(angle) * radius);
int bz = z + (int)round(sin(angle) * radius);
block_list_push(out, (bx + x) / 2, level_y - 1, (bz + z) / 2, (uint16_t)arch->log_block);
place_leaf_circle(bx, level_y, bz, 1, arch->leaf_block, rng, 0.1, out);
}
}
place_leaf_circle(x, y + trunk_height + 2, z, 2, arch->leaf_block, rng, 0.0, out);
}
static void build_maple_spread(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height - 1;
if (trunk_height < 5) trunk_height = 5;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int canopy_base = y + trunk_height - 1;
int leaf_top = canopy_base + 4;
extend_trunk_to(x, y, z, trunk_height, leaf_top - y, arch->log_block, out);
for (int cy = canopy_base; cy <= leaf_top; ++cy) {
int rel = cy - canopy_base;
int radius = 3 + (rel <= 1 ? 1 : -rel / 2);
if (radius < 1) radius = 1;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, 0.25, out);
}
for (int b = 0; b < 6; ++b) {
double angle = (double)b / 6.0 * 6.28318 + rng_next_f64(rng) * 0.2;
int bx = x + (int)round(cos(angle) * 3.5);
int bz = z + (int)round(sin(angle) * 3.5);
block_list_push(out, bx, canopy_base - 1, bz, (uint16_t)arch->log_block);
place_leaf_circle(bx, canopy_base - 1, bz, 2, arch->leaf_block, rng, 0.35, out);
}
}
static void build_oak_ancient(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 5 + rng_range_inclusive(rng, 0, 3);
if (trunk_height < 10) trunk_height = 10;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int canopy_start = y + trunk_height - 6;
int canopy_top = canopy_start + 6;
reinforce_trunk(x, y, z, canopy_top + 2, arch->log_block, out);
const int dirs[8][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
for (int i = 0; i < 8; ++i) {
int len = 3 + rng_range_inclusive(rng, 1, 3);
int lift = (i < 4) ? 2 : 3;
place_branch_span(x, canopy_start - 1 - (i % 2), z, dirs[i][0], dirs[i][1], len + 1, lift, arch->log_block, arch->leaf_block, rng, out);
}
place_leaf_blob(x, canopy_start, z, 5, 3, arch->leaf_block, rng, out);
place_leaf_circle(x, canopy_top + 1, z, 3, arch->leaf_block, rng, 0.15, out);
for (int i = 0; i < 4; ++i) {
int butt = rng_range_inclusive(rng, 1, 2);
block_list_push(out, x + dirs[i][0], y + butt, z + dirs[i][1], (uint16_t)arch->log_block);
}
}
static void build_spruce_ancient(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 6 + rng_range_inclusive(rng, 0, 2);
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int leaf_top = y + trunk_height + 3;
reinforce_trunk(x, y, z, leaf_top, arch->log_block, out);
int tiers = 5 + rng_range_inclusive(rng, 0, 1);
for (int t = 0; t < tiers; ++t) {
int cy = y + trunk_height - t * 3;
if (cy < y + 3) break;
int radius = 4 - t / 2;
if (radius < 2) radius = 2;
place_leaf_circle(x, cy, z, radius, arch->leaf_block, rng, 0.18, out);
place_leaf_circle(x, cy - 1, z, radius - 1, arch->leaf_block, rng, 0.25, out);
if (rng_next_f64(rng) < 0.6) {
int dir = rng_range_inclusive(rng, 0, 3);
const int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
place_branch_span(x, cy - 1, z, dirs[dir][0], dirs[dir][1], 2 + rng_range_inclusive(rng, 1, 2), 3, arch->log_block, arch->leaf_block, rng, out);
}
}
place_leaf_circle(x, leaf_top, z, 2, arch->leaf_block, rng, 0.0, out);
place_leaf_circle(x, leaf_top + 1, z, 1, arch->leaf_block, rng, 0.0, out);
}
static void build_maple_ancient(worldgen_ctx *ctx, int x, int y, int z, int height, rng_state *rng, block_list *out, const tree_archetype *arch) {
(void)ctx;
int trunk_height = height + 4;
place_log_column(x, y, z, trunk_height, arch->log_block, out);
int branch_base = y + trunk_height - 4;
int canopy_top = branch_base + 5;
reinforce_trunk(x, y, z, canopy_top + 1, arch->log_block, out);
const int dirs[6][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}};
for (int i = 0; i < 6; ++i) {
int len = 3 + rng_range_inclusive(rng, 1, 3);
place_branch_span(x, branch_base - (i % 2), z, dirs[i][0], dirs[i][1], len, 2, arch->log_block, arch->leaf_block, rng, out);
}
place_leaf_blob(x, branch_base, z, 5, 3, arch->leaf_block, rng, out);
place_leaf_circle(x, canopy_top + 1, z, 3, arch->leaf_block, rng, 0.1, out);
}
typedef enum {
TREE_OAK_ROUND = 0,
TREE_OAK_SPRAWL,
TREE_OAK_COLUMNAR,
TREE_BIRCH_SLENDER,
TREE_BIRCH_BRUSH,
TREE_BIRCH_FAN,
TREE_PINE_SPIRE,
TREE_PINE_CROWN,
TREE_SPRUCE_TIERS,
TREE_SPRUCE_CONE,
TREE_SPRUCE_SPREAD,
TREE_WALNUT_TABLE,
TREE_WALNUT_VASE,
TREE_CYPRESS_COLUMN,
TREE_CYPRESS_FAN,
TREE_MAPLE_TANGLE,
TREE_MAPLE_SPREAD,
TREE_OAK_ANCIENT,
TREE_SPRUCE_ANCIENT,
TREE_MAPLE_ANCIENT,
TREE_COUNT
} tree_kind;
static const tree_archetype TREE_TYPES[TREE_COUNT] = {
[TREE_OAK_ROUND] = {"oak_round", TREE_SPECIES_OAK, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 6, 9, 3, 4, build_oak_round},
[TREE_OAK_SPRAWL] = {"oak_sprawl", TREE_SPECIES_OAK, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 7, 11, 4, 5, build_oak_sprawl},
[TREE_OAK_COLUMNAR] = {"oak_columnar", TREE_SPECIES_OAK, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 7, 11, 3, 5, build_oak_columnar},
[TREE_BIRCH_SLENDER] = {"birch_slender", TREE_SPECIES_BIRCH, BLOCK_BIRCH_LOG, BLOCK_BIRCH_LEAVES, 6, 9, 2, 4, build_birch_slender},
[TREE_BIRCH_BRUSH] = {"birch_brush", TREE_SPECIES_BIRCH, BLOCK_BIRCH_LOG, BLOCK_BIRCH_LEAVES, 6, 9, 3, 4, build_birch_brush},
[TREE_BIRCH_FAN] = {"birch_fan", TREE_SPECIES_BIRCH, BLOCK_BIRCH_LOG, BLOCK_BIRCH_LEAVES, 7, 10, 3, 4, build_birch_fan},
[TREE_PINE_SPIRE] = {"pine_spire", TREE_SPECIES_PINE, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 9, 13, 3, 5, build_pine_spire},
[TREE_PINE_CROWN] = {"pine_crown", TREE_SPECIES_PINE, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 9, 13, 3, 5, build_pine_crown},
[TREE_SPRUCE_TIERS] = {"spruce_tiers", TREE_SPECIES_SPRUCE, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 10, 14, 3, 6, build_spruce_tiers},
[TREE_SPRUCE_CONE] = {"spruce_cone", TREE_SPECIES_SPRUCE, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 10, 14, 3, 6, build_spruce_cone},
[TREE_SPRUCE_SPREAD] = {"spruce_spread", TREE_SPECIES_SPRUCE, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 9, 13, 3, 6, build_spruce_spread},
[TREE_WALNUT_TABLE] = {"walnut_table", TREE_SPECIES_WALNUT, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 6, 9, 4, 4, build_walnut_table},
[TREE_WALNUT_VASE] = {"walnut_vase", TREE_SPECIES_WALNUT, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 6, 9, 4, 4, build_walnut_vase},
[TREE_CYPRESS_COLUMN] = {"cypress_column", TREE_SPECIES_CYPRESS, BLOCK_BIRCH_LOG, BLOCK_BIRCH_LEAVES, 8, 12, 2, 6, build_cypress_column},
[TREE_CYPRESS_FAN] = {"cypress_fan", TREE_SPECIES_CYPRESS, BLOCK_BIRCH_LOG, BLOCK_BIRCH_LEAVES, 8, 12, 3, 6, build_cypress_fan},
[TREE_MAPLE_TANGLE] = {"maple_tangle", TREE_SPECIES_MAPLE, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 7, 10, 4, 5, build_maple_tangle},
[TREE_MAPLE_SPREAD] = {"maple_spread", TREE_SPECIES_MAPLE, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 7, 10, 4, 5, build_maple_spread},
[TREE_OAK_ANCIENT] = {"oak_ancient", TREE_SPECIES_OAK, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 12, 16, 6, 7, build_oak_ancient},
[TREE_SPRUCE_ANCIENT] = {"spruce_ancient", TREE_SPECIES_SPRUCE, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 12, 16, 5, 7, build_spruce_ancient},
[TREE_MAPLE_ANCIENT] = {"maple_ancient", TREE_SPECIES_MAPLE, BLOCK_OAK_LOG, BLOCK_OAK_LEAVES, 11, 15, 6, 6, build_maple_ancient},
};
static const int TREE_VARIANTS_OAK[] = {TREE_OAK_ROUND, TREE_OAK_SPRAWL, TREE_OAK_COLUMNAR, TREE_OAK_ANCIENT};
static const int TREE_VARIANTS_BIRCH[] = {TREE_BIRCH_SLENDER, TREE_BIRCH_BRUSH, TREE_BIRCH_FAN};
static const int TREE_VARIANTS_SPRUCE[] = {TREE_SPRUCE_TIERS, TREE_SPRUCE_CONE, TREE_SPRUCE_SPREAD, TREE_SPRUCE_ANCIENT};
static const int TREE_VARIANTS_PINE[] = {TREE_PINE_SPIRE, TREE_PINE_CROWN};
static const int TREE_VARIANTS_WALNUT[] = {TREE_WALNUT_TABLE, TREE_WALNUT_VASE};
static const int TREE_VARIANTS_CYPRESS[] = {TREE_CYPRESS_COLUMN, TREE_CYPRESS_FAN};
static const int TREE_VARIANTS_MAPLE[] = {TREE_MAPLE_TANGLE, TREE_MAPLE_SPREAD, TREE_MAPLE_ANCIENT};
static const tree_species_catalog TREE_VARIANT_CATALOG[] = {
{TREE_SPECIES_OAK, "oak", TREE_VARIANTS_OAK, sizeof(TREE_VARIANTS_OAK) / sizeof(TREE_VARIANTS_OAK[0])},
{TREE_SPECIES_BIRCH, "birch", TREE_VARIANTS_BIRCH, sizeof(TREE_VARIANTS_BIRCH) / sizeof(TREE_VARIANTS_BIRCH[0])},
{TREE_SPECIES_SPRUCE, "spruce", TREE_VARIANTS_SPRUCE, sizeof(TREE_VARIANTS_SPRUCE) / sizeof(TREE_VARIANTS_SPRUCE[0])},
{TREE_SPECIES_PINE, "pine", TREE_VARIANTS_PINE, sizeof(TREE_VARIANTS_PINE) / sizeof(TREE_VARIANTS_PINE[0])},
{TREE_SPECIES_WALNUT, "walnut", TREE_VARIANTS_WALNUT, sizeof(TREE_VARIANTS_WALNUT) / sizeof(TREE_VARIANTS_WALNUT[0])},
{TREE_SPECIES_CYPRESS, "cypress", TREE_VARIANTS_CYPRESS, sizeof(TREE_VARIANTS_CYPRESS) / sizeof(TREE_VARIANTS_CYPRESS[0])},
{TREE_SPECIES_MAPLE, "maple", TREE_VARIANTS_MAPLE, sizeof(TREE_VARIANTS_MAPLE) / sizeof(TREE_VARIANTS_MAPLE[0])}
};
static const tree_species_catalog *get_species_catalog(tree_species species) {
size_t count = sizeof(TREE_VARIANT_CATALOG) / sizeof(TREE_VARIANT_CATALOG[0]);
for (size_t i = 0; i < count; ++i) {
if (TREE_VARIANT_CATALOG[i].species == species) {
return &TREE_VARIANT_CATALOG[i];
}
}
return NULL;
}
static const tree_archetype *choose_species_variant(tree_species species, rng_state *rng) {
const tree_species_catalog *catalog = get_species_catalog(species);
if (!catalog || catalog->variant_count == 0) return NULL;
size_t idx = (size_t)rng_range_inclusive(rng, 0, (int)catalog->variant_count - 1);
return &TREE_TYPES[catalog->variants[idx]];
}
static const int TREE_POOL_WEST[] = {
TREE_OAK_ROUND, TREE_OAK_SPRAWL, TREE_OAK_COLUMNAR,
TREE_WALNUT_TABLE, TREE_WALNUT_VASE,
TREE_MAPLE_TANGLE, TREE_MAPLE_SPREAD,
TREE_CYPRESS_COLUMN, TREE_CYPRESS_FAN
};
static const int TREE_POOL_EAST_LOW[] = {
TREE_OAK_ROUND, TREE_OAK_SPRAWL, TREE_OAK_COLUMNAR,
TREE_MAPLE_TANGLE, TREE_MAPLE_SPREAD,
TREE_BIRCH_SLENDER, TREE_BIRCH_BRUSH, TREE_BIRCH_FAN,
TREE_PINE_SPIRE
};
static const int TREE_POOL_EAST_HIGH[] = {
TREE_BIRCH_SLENDER, TREE_BIRCH_BRUSH, TREE_BIRCH_FAN,
TREE_PINE_SPIRE, TREE_PINE_CROWN,
TREE_SPRUCE_TIERS, TREE_SPRUCE_CONE, TREE_SPRUCE_SPREAD,
TREE_CYPRESS_COLUMN
};
static const int TREE_POOL_OLD_GROWTH[] = {
TREE_OAK_ANCIENT, TREE_SPRUCE_ANCIENT, TREE_MAPLE_ANCIENT,
TREE_OAK_SPRAWL, TREE_MAPLE_SPREAD, TREE_WALNUT_VASE,
TREE_SPRUCE_TIERS, TREE_PINE_CROWN
};
static const tree_archetype *choose_tree_archetype(worldgen_ctx *ctx, const column_data *data, rng_state *rng) {
int altitude = data->height - ctx->sea_level;
const tree_archetype *fallback = &TREE_TYPES[TREE_OAK_ROUND];
int snow_line = ctx->snow_line;
if (data->height >= snow_line - 2) {
const tree_archetype *snow_pick = choose_species_variant(TREE_SPECIES_SPRUCE, rng);
if (snow_pick && rng_next_f64(rng) < 0.9) return snow_pick;
}
if (data->biome == BIOME_OLD_GROWTH_PLAINS) {
size_t pool_size = sizeof(TREE_POOL_OLD_GROWTH) / sizeof(TREE_POOL_OLD_GROWTH[0]);
size_t idx = (size_t)rng_range_inclusive(rng, 0, (int)pool_size - 1);
return &TREE_TYPES[TREE_POOL_OLD_GROWTH[idx]];
}
if (data->biome == BIOME_WEST_KY_COALFIELDS) {
size_t pool_size = sizeof(TREE_POOL_WEST) / sizeof(TREE_POOL_WEST[0]);
size_t idx = (size_t)rng_range_inclusive(rng, 0, (int)pool_size - 1);
return &TREE_TYPES[TREE_POOL_WEST[idx]];
}
const int *pool = (altitude > 30) ? TREE_POOL_EAST_HIGH : TREE_POOL_EAST_LOW;
size_t pool_size = (altitude > 30) ? sizeof(TREE_POOL_EAST_HIGH) / sizeof(TREE_POOL_EAST_HIGH[0])
: sizeof(TREE_POOL_EAST_LOW) / sizeof(TREE_POOL_EAST_LOW[0]);
size_t idx = (size_t)rng_range_inclusive(rng, 0, (int)pool_size - 1);
const tree_archetype *choice = &TREE_TYPES[pool[idx]];
return choice ? choice : fallback;
}
static void generate_tree(worldgen_ctx *ctx, const tree_archetype *arch, int x, int y, int z, rng_state *rng, block_list *out) {
if (!arch) return;
int height = rng_range_inclusive(rng, arch->min_height, arch->max_height);
if (!can_place_tree(ctx, x, y, z, height, arch->space_radius, arch->canopy_extra)) {
if (!can_place_tree(ctx, x, y, z, height, arch->space_radius - 1 >= 1 ? arch->space_radius - 1 : 1, arch->canopy_extra)) return;
}
clear_snow_block(ctx, x, y, z, out);
arch->builder(ctx, x, y, z, height, rng, out, arch);
}
static void generate_chunk_grass(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out) {
for (int dx = 0; dx < CHUNK_SIZE; ++dx) {
for (int dz = 0; dz < CHUNK_SIZE; ++dz) {
column_data cd = columns[dx][dz];
if (cd.height <= 0 || cd.height >= CHUNK_HEIGHT - 1) continue;
if (cd.height >= ctx->snow_line - 1) continue;
int world_x = chunk_x * CHUNK_SIZE + dx;
int world_z = chunk_z * CHUNK_SIZE + dz;
if (out->blocks[cd.height][dx][dz] != BLOCK_GRASS) continue;
if (out->blocks[cd.height + 1][dx][dz] != BLOCK_AIR) continue;
if (ground_slope(ctx, world_x, world_z) > 3) continue;
double density = tree_density_mask(ctx, world_x, world_z);
double meadow = simplex_noise2(&ctx->noise, (world_x + 1200) * 0.02, (world_z - 1200) * 0.02) * 0.5 + 0.5;
double humidity = simplex_noise2(&ctx->noise, (world_x - 9000) * 0.01, (world_z + 9000) * 0.01) * 0.5 + 0.5;
double chance = 0.15 + meadow * 0.25 + humidity * 0.2 + (1.0 - density) * 0.2;
if (cd.height < ctx->sea_level) chance *= 0.7;
chance = clamp01(chance);
uint32_t h = hash_coords(world_x, world_z, (uint32_t)(ctx->world_seed ^ 0x5F3759DFu));
double roll = (double)(h & 0xFFFF) / 65535.0;
if (roll > chance) continue;
out->blocks[cd.height + 1][dx][dz] = BLOCK_TALL_GRASS;
if (roll < chance * 0.4) {
const int offsets[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int i = 0; i < 4; ++i) {
int nx = dx + offsets[i][0];
int nz = dz + offsets[i][1];
if (nx < 0 || nx >= CHUNK_SIZE || nz < 0 || nz >= CHUNK_SIZE) continue;
column_data nd = columns[nx][nz];
if (nd.height <= 0 || nd.height >= CHUNK_HEIGHT - 1) continue;
if (out->blocks[nd.height][nx][nz] != BLOCK_GRASS) continue;
if (out->blocks[nd.height + 1][nx][nz] != BLOCK_AIR) continue;
out->blocks[nd.height + 1][nx][nz] = BLOCK_TALL_GRASS;
}
}
}
}
}
static void generate_chunk_flowers(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out) {
for (int dx = 0; dx < CHUNK_SIZE; ++dx) {
for (int dz = 0; dz < CHUNK_SIZE; ++dz) {
column_data cd = columns[dx][dz];
if (cd.height <= 0 || cd.height >= CHUNK_HEIGHT - 2) continue;
int world_x = chunk_x * CHUNK_SIZE + dx;
int world_z = chunk_z * CHUNK_SIZE + dz;
if (out->blocks[cd.height][dx][dz] != BLOCK_GRASS) continue;
if (out->blocks[cd.height + 1][dx][dz] != BLOCK_AIR) continue;
int slope = ground_slope(ctx, world_x, world_z);
if (slope > 3) continue;
double meadow = simplex_noise2(&ctx->noise, (world_x + 5400) * 0.012, (world_z - 5400) * 0.012) * 0.5 + 0.5;
double bloom = simplex_noise2(&ctx->noise, (world_x - 8100) * 0.0035, (world_z + 8100) * 0.0035) * 0.5 + 0.5;
double humidity = simplex_noise2(&ctx->noise, (world_x + 12000) * 0.0045, (world_z - 12000) * 0.0045) * 0.5 + 0.5;
double fertility = clamp01(meadow * 0.6 + humidity * 0.4);
double chance = 0.03 + fertility * 0.18 + bloom * 0.12;
if (cd.height < ctx->sea_level) chance *= 0.6;
double slope_penalty = clamp01((double)slope / 4.0);
chance *= (1.0 - slope_penalty * 0.7);
uint32_t h = hash_coords(world_x + 30000, world_z - 30000, (uint32_t)(ctx->world_seed ^ 0xA511E9B5u));
double roll = (double)(h & 0xFFFF) / 65535.0;
if (roll > chance) continue;
out->blocks[cd.height + 1][dx][dz] = BLOCK_WILDFLOWER;
static const int offsets[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int i = 0; i < 4; ++i) {
int nx = dx + offsets[i][0];
int nz = dz + offsets[i][1];
if (nx < 0 || nx >= CHUNK_SIZE || nz < 0 || nz >= CHUNK_SIZE) continue;
column_data nd = columns[nx][nz];
if (nd.height <= 0 || nd.height >= CHUNK_HEIGHT - 2) continue;
if (out->blocks[nd.height][nx][nz] != BLOCK_GRASS) continue;
if (out->blocks[nd.height + 1][nx][nz] != BLOCK_AIR) continue;
uint32_t nh = hash_coords(world_x + offsets[i][0] + 60000, world_z + offsets[i][1] - 60000, (uint32_t)(ctx->world_seed ^ 0xF00DBAAu));
if ((nh & 0xFFFF) > 24000) continue;
out->blocks[nd.height + 1][nx][nz] = BLOCK_WILDFLOWER;
}
}
}
}
static void place_log_line(chunk_data *chunk, int chunk_origin_x, int chunk_origin_z,
int x0, int z0, int x1, int z1, int y, uint16_t block,
int door_x, int door_z, int door_clear_top,
rng_state *rng, double skip_prob) {
int dx = (x1 > x0) ? 1 : (x1 < x0 ? -1 : 0);
int dz = (z1 > z0) ? 1 : (z1 < z0 ? -1 : 0);
int length = (dx != 0) ? abs(x1 - x0) : abs(z1 - z0);
int respect_door = (door_x != INT_MIN && door_z != INT_MIN);
for (int i = 0; i <= length; ++i) {
int wx = x0 + dx * i;
int wz = z0 + dz * i;
if (respect_door && wx == door_x && wz == door_z && y <= door_clear_top) continue;
if (rng && skip_prob > 0.0 && rng_next_f64(rng) < skip_prob) continue;
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
set_block_with_height(chunk, lx, lz, y, block);
}
}
typedef struct {
int offset_x;
int offset_z;
int half_w;
int half_l;
} cabin_rect;
#define CABIN_MAX_RECTS 4
typedef struct {
const cabin_rect *rects;
size_t rect_count;
int wall_height;
int stories;
uint16_t floor_block;
uint16_t roof_block;
uint16_t log_x_block;
uint16_t log_z_block;
uint16_t corner_log;
uint16_t stair_e_block;
uint16_t stair_w_block;
uint16_t window_block;
uint16_t foundation_block;
double log_decay;
double plank_decay;
double window_chance;
double door_decay;
int door_rect_index;
int has_ladder;
int path_width;
} cabin_blueprint;
static const cabin_rect CABIN_RECT_SMALL[] = {
{0, 0, 2, 3}
};
static const cabin_rect CABIN_RECT_LONG[] = {
{0, 0, 3, 4}
};
static const cabin_rect CABIN_RECT_TWO_STORY[] = {
{0, 0, 3, 3}
};
static const cabin_blueprint CABIN_BLUEPRINTS[] = {
{
CABIN_RECT_SMALL,
sizeof(CABIN_RECT_SMALL) / sizeof(CABIN_RECT_SMALL[0]),
3,
1,
BLOCK_SPRUCE_PLANKS,
BLOCK_OAK_PLANKS,
BLOCK_OAK_LOG_X,
BLOCK_OAK_LOG_Z,
BLOCK_OAK_LOG,
BLOCK_SPRUCE_STAIRS_E,
BLOCK_SPRUCE_STAIRS_W,
BLOCK_GLASS_PANE,
BLOCK_STONE,
0.04,
0.25,
0.45,
0.08,
0,
0,
2
},
{
CABIN_RECT_LONG,
sizeof(CABIN_RECT_LONG) / sizeof(CABIN_RECT_LONG[0]),
4,
1,
BLOCK_OAK_PLANKS,
BLOCK_SPRUCE_PLANKS,
BLOCK_SPRUCE_LOG_X,
BLOCK_SPRUCE_LOG_Z,
BLOCK_OAK_LOG,
BLOCK_SPRUCE_STAIRS_E,
BLOCK_SPRUCE_STAIRS_W,
BLOCK_GLASS_PANE,
BLOCK_STONE,
0.05,
0.3,
0.4,
0.1,
0,
0,
3
},
{
CABIN_RECT_TWO_STORY,
sizeof(CABIN_RECT_TWO_STORY) / sizeof(CABIN_RECT_TWO_STORY[0]),
4,
2,
BLOCK_SPRUCE_PLANKS,
BLOCK_SPRUCE_PLANKS,
BLOCK_OAK_LOG_X,
BLOCK_OAK_LOG_Z,
BLOCK_OAK_LOG,
BLOCK_SPRUCE_STAIRS_E,
BLOCK_SPRUCE_STAIRS_W,
BLOCK_GLASS_PANE,
BLOCK_STONE,
0.04,
0.22,
0.5,
0.08,
0,
1,
2
}
};
static void update_columns_for_rect(column_data columns[CHUNK_SIZE][CHUNK_SIZE], int chunk_origin_x, int chunk_origin_z,
int x0, int x1, int z0, int z1, int new_height) {
for (int wz = z0 - 1; wz <= z1 + 1; ++wz) {
for (int wx = x0 - 1; wx <= x1 + 1; ++wx) {
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
if (columns[lx][lz].height < new_height) {
columns[lx][lz].height = new_height;
columns[lx][lz].has_water = 0;
}
}
}
}
static int rect_overlaps_mask(unsigned char occupancy[CHUNK_SIZE][CHUNK_SIZE], int chunk_origin_x, int chunk_origin_z,
int x0, int x1, int z0, int z1, int padding) {
for (int wz = z0 - padding; wz <= z1 + padding; ++wz) {
for (int wx = x0 - padding; wx <= x1 + padding; ++wx) {
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
if (occupancy[lx][lz]) return 1;
}
}
return 0;
}
static void mark_rect_occupancy(unsigned char occupancy[CHUNK_SIZE][CHUNK_SIZE], int chunk_origin_x, int chunk_origin_z,
int x0, int x1, int z0, int z1, int padding) {
for (int wz = z0 - padding; wz <= z1 + padding; ++wz) {
for (int wx = x0 - padding; wx <= x1 + padding; ++wx) {
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
occupancy[lx][lz] = 1;
}
}
}
static void build_cabin_roof(const cabin_blueprint *bp, int chunk_x, int chunk_z, chunk_data *chunk,
int rect_center_x, int rect_center_z, int half_w, int half_l,
int roof_base_y, rng_state *rng) {
int chunk_origin_x = chunk_x * CHUNK_SIZE;
int chunk_origin_z = chunk_z * CHUNK_SIZE;
int overhang = 1;
int x0 = rect_center_x - half_w;
int x1 = rect_center_x + half_w;
int z0 = rect_center_z - half_l;
int z1 = rect_center_z + half_l;
int layers = half_w + 3;
for (int layer = 0; layer < layers; ++layer) {
int start_x = (x0 - overhang) + layer;
int end_x = (x1 + overhang) - layer;
if (start_x > end_x) break;
int y = roof_base_y + layer + 1;
for (int wz = z0 - overhang; wz <= z1 + overhang; ++wz) {
for (int wx = start_x; wx <= end_x; ++wx) {
if (rng && bp->plank_decay > 0.0 && rng_next_f64(rng) < bp->plank_decay * 0.2) continue;
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
set_block_with_height(chunk, lx, lz, y, bp->roof_block);
}
}
for (int wz = z0 - overhang; wz <= z1 + overhang; ++wz) {
int east_x = end_x + 1;
int west_x = start_x - 1;
int lx_e = east_x - chunk_origin_x;
int lx_w = west_x - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx_e >= 0 && lx_e < CHUNK_SIZE && lz >= 0 && lz < CHUNK_SIZE) {
set_block_with_height(chunk, lx_e, lz, y, bp->stair_w_block);
}
if (lx_w >= 0 && lx_w < CHUNK_SIZE && lz >= 0 && lz < CHUNK_SIZE) {
set_block_with_height(chunk, lx_w, lz, y, bp->stair_e_block);
}
}
}
}
static void carve_cabin_path(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *chunk, column_data columns[CHUNK_SIZE][CHUNK_SIZE],
int start_x, int start_z, int dir_x, int dir_z, int length, int width) {
if (dir_x == 0 && dir_z == 0) return;
int chunk_min_x = chunk_x * CHUNK_SIZE;
int chunk_max_x = chunk_min_x + CHUNK_SIZE - 1;
int chunk_min_z = chunk_z * CHUNK_SIZE;
int chunk_max_z = chunk_min_z + CHUNK_SIZE - 1;
int end_x = start_x + dir_x * length;
int end_z = start_z + dir_z * length;
if (end_x < chunk_min_x + 1) end_x = chunk_min_x + 1;
if (end_x > chunk_max_x - 1) end_x = chunk_max_x - 1;
if (end_z < chunk_min_z + 1) end_z = chunk_min_z + 1;
if (end_z > chunk_max_z - 1) end_z = chunk_max_z - 1;
carve_trail_span(ctx, chunk_x, chunk_z, chunk, columns, start_x, start_z, end_x, end_z, width);
}
static void place_wall_window(chunk_data *chunk, int chunk_origin_x, int chunk_origin_z,
int wx, int wz, int window_y, uint16_t window_block, int height) {
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) return;
for (int h = 0; h < height; ++h) {
int y = window_y + h;
if (y >= CHUNK_HEIGHT) break;
set_block_with_height(chunk, lx, lz, y, window_block);
}
}
static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const cabin_rect *rect,
int chunk_x, int chunk_z, chunk_data *chunk, column_data columns[CHUNK_SIZE][CHUNK_SIZE],
int rect_center_x, int rect_center_z, int base_floor_y,
int door_x, int door_z, int door_side, int has_door, rng_state *rng) {
(void)ctx;
int chunk_origin_x = chunk_x * CHUNK_SIZE;
int chunk_origin_z = chunk_z * CHUNK_SIZE;
int x0 = rect_center_x - rect->half_w;
int x1 = rect_center_x + rect->half_w;
int z0 = rect_center_z - rect->half_l;
int z1 = rect_center_z + rect->half_l;
int clear_top = base_floor_y + bp->wall_height * bp->stories + 6;
for (int wz = z0 - 2; wz <= z1 + 2; ++wz) {
for (int wx = x0 - 2; wx <= x1 + 2; ++wx) {
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
for (int y = base_floor_y; y <= clear_top && y < CHUNK_HEIGHT; ++y) {
set_block_with_height(chunk, lx, lz, y, BLOCK_AIR);
}
}
}
uint16_t foundation_block = bp->foundation_block ? bp->foundation_block : BLOCK_STONE;
for (int wz = z0; wz <= z1; ++wz) {
for (int wx = x0; wx <= x1; ++wx) {
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
int column_height = columns[lx][lz].height;
if (column_height < base_floor_y) {
for (int y = column_height + 1; y <= base_floor_y; ++y) {
set_block_with_height(chunk, lx, lz, y, foundation_block);
}
columns[lx][lz].height = base_floor_y;
}
}
}
int ladder_hole_x = INT_MIN;
int ladder_hole_z = INT_MIN;
for (int story = 0; story < bp->stories; ++story) {
int story_floor = base_floor_y + story * bp->wall_height;
if (story_floor >= CHUNK_HEIGHT - 4) break;
for (int wz = z0 + 1; wz <= z1 - 1; ++wz) {
for (int wx = x0 + 1; wx <= x1 - 1; ++wx) {
if (story > 0 && wx == ladder_hole_x && wz == ladder_hole_z) continue;
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
set_block_with_height(chunk, lx, lz, story_floor, bp->floor_block);
}
}
int story_has_door = has_door && story == 0;
int door_clear_top = story_has_door ? (story_floor + 2) : story_floor;
int door_x_story = story_has_door ? door_x : INT_MIN;
int door_z_story = story_has_door ? door_z : INT_MIN;
for (int h = 0; h < bp->wall_height; ++h) {
int yy = story_floor + 1 + h;
if (yy >= CHUNK_HEIGHT - 2) break;
int extend_x = ((story * bp->wall_height + h) % 2 == 0);
double base_skip = bp->log_decay;
double extend_skip = bp->log_decay * 0.6;
int north_start_x = extend_x ? x0 - 1 : x0;
int north_end_x = extend_x ? x1 + 1 : x1;
int south_start_x = extend_x ? x0 - 1 : x0;
int south_end_x = extend_x ? x1 + 1 : x1;
if (extend_x) {
place_log_line(chunk, chunk_origin_x, chunk_origin_z, x0, z0, x0, z1, yy, bp->log_z_block,
door_x_story, door_z_story, door_clear_top, rng, base_skip);
place_log_line(chunk, chunk_origin_x, chunk_origin_z, x1, z0, x1, z1, yy, bp->log_z_block,
door_x_story, door_z_story, door_clear_top, rng, base_skip);
place_log_line(chunk, chunk_origin_x, chunk_origin_z, north_start_x, z0, north_end_x, z0, yy, bp->log_x_block,
door_x_story, door_z_story, door_clear_top, rng, extend_skip);
place_log_line(chunk, chunk_origin_x, chunk_origin_z, south_start_x, z1, south_end_x, z1, yy, bp->log_x_block,
door_x_story, door_z_story, door_clear_top, rng, extend_skip);
} else {
int west_start_z = z0 - 1;
int west_end_z = z1 + 1;
int east_start_z = z0 - 1;
int east_end_z = z1 + 1;
place_log_line(chunk, chunk_origin_x, chunk_origin_z, north_start_x, z0, north_end_x, z0, yy, bp->log_x_block,
door_x_story, door_z_story, door_clear_top, rng, base_skip);
place_log_line(chunk, chunk_origin_x, chunk_origin_z, south_start_x, z1, south_end_x, z1, yy, bp->log_x_block,
door_x_story, door_z_story, door_clear_top, rng, base_skip);
place_log_line(chunk, chunk_origin_x, chunk_origin_z, x0, west_start_z, x0, west_end_z, yy, bp->log_z_block,
door_x_story, door_z_story, door_clear_top, rng, extend_skip);
place_log_line(chunk, chunk_origin_x, chunk_origin_z, x1, east_start_z, x1, east_end_z, yy, bp->log_z_block,
door_x_story, door_z_story, door_clear_top, rng, extend_skip);
}
}
if (bp->window_block && bp->window_chance > 0.0) {
int window_y = story_floor + 2;
int window_height = (bp->wall_height >= 3) ? 2 : 1;
int min_win_x = x0 + 2;
int max_win_x = x1 - 2;
if (min_win_x > max_win_x) {
min_win_x = x0 + 1;
max_win_x = x1 - 1;
}
int min_win_z = z0 + 2;
int max_win_z = z1 - 2;
if (min_win_z > max_win_z) {
min_win_z = z0 + 1;
max_win_z = z1 - 1;
}
for (int wx = min_win_x; wx <= max_win_x; wx += 2) {
if (story_has_door && door_side == 0 && wx == door_x) continue;
if (rng_next_f64(rng) < bp->window_chance) {
place_wall_window(chunk, chunk_origin_x, chunk_origin_z, wx, z0, window_y, bp->window_block, window_height);
}
if (story_has_door && door_side == 1 && wx == door_x) continue;
if (rng_next_f64(rng) < bp->window_chance) {
place_wall_window(chunk, chunk_origin_x, chunk_origin_z, wx, z1, window_y, bp->window_block, window_height);
}
}
for (int wz = min_win_z; wz <= max_win_z; wz += 2) {
if (story_has_door && door_side == 2 && wz == door_z) continue;
if (rng_next_f64(rng) < bp->window_chance) {
place_wall_window(chunk, chunk_origin_x, chunk_origin_z, x0, wz, window_y, bp->window_block, window_height);
}
if (story_has_door && door_side == 3 && wz == door_z) continue;
if (rng_next_f64(rng) < bp->window_chance) {
place_wall_window(chunk, chunk_origin_x, chunk_origin_z, x1, wz, window_y, bp->window_block, window_height);
}
}
}
if (story_has_door) {
if (rng_next_f64(rng) >= bp->door_decay) {
uint16_t lower = BLOCK_SPRUCE_DOOR_S_LOWER;
uint16_t upper = BLOCK_SPRUCE_DOOR_S_UPPER;
switch (door_side) {
case 0: lower = BLOCK_SPRUCE_DOOR_N_LOWER; upper = BLOCK_SPRUCE_DOOR_N_UPPER; break;
case 1: lower = BLOCK_SPRUCE_DOOR_S_LOWER; upper = BLOCK_SPRUCE_DOOR_S_UPPER; break;
case 2: lower = BLOCK_SPRUCE_DOOR_W_LOWER; upper = BLOCK_SPRUCE_DOOR_W_UPPER; break;
case 3: lower = BLOCK_SPRUCE_DOOR_E_LOWER; upper = BLOCK_SPRUCE_DOOR_E_UPPER; break;
}
int lx = door_x - chunk_origin_x;
int lz = door_z - chunk_origin_z;
if (lx >= 0 && lx < CHUNK_SIZE && lz >= 0 && lz < CHUNK_SIZE) {
set_block_with_height(chunk, lx, lz, story_floor + 1, lower);
set_block_with_height(chunk, lx, lz, story_floor + 2, upper);
}
}
}
if (bp->has_ladder && story < bp->stories - 1) {
int ladder_x = rect_center_x;
int ladder_z = rect_center_z;
uint16_t ladder_block = BLOCK_LADDER_S;
switch (door_side) {
case 0: ladder_z = z1 - 2; ladder_x = rect_center_x; ladder_block = BLOCK_LADDER_N; break;
case 1: ladder_z = z0 + 2; ladder_x = rect_center_x; ladder_block = BLOCK_LADDER_S; break;
case 2: ladder_x = x1 - 2; ladder_z = rect_center_z; ladder_block = BLOCK_LADDER_E; break;
case 3: ladder_x = x0 + 2; ladder_z = rect_center_z; ladder_block = BLOCK_LADDER_W; break;
}
ladder_x = clamp_int(ladder_x, x0 + 1, x1 - 1);
ladder_z = clamp_int(ladder_z, z0 + 1, z1 - 1);
int lx = ladder_x - chunk_origin_x;
int lz = ladder_z - chunk_origin_z;
if (lx >= 0 && lx < CHUNK_SIZE && lz >= 0 && lz < CHUNK_SIZE) {
for (int y = story_floor + 1; y <= story_floor + bp->wall_height; ++y) {
set_block_with_height(chunk, lx, lz, y, ladder_block);
}
int next_floor = story_floor + bp->wall_height;
if (next_floor < CHUNK_HEIGHT) {
set_block_with_height(chunk, lx, lz, next_floor, BLOCK_AIR);
}
ladder_hole_x = ladder_x;
ladder_hole_z = ladder_z;
}
}
}
int roof_base = base_floor_y + bp->wall_height * bp->stories;
build_cabin_roof(bp, chunk_x, chunk_z, chunk, rect_center_x, rect_center_z, rect->half_w, rect->half_l, roof_base, rng);
int attic_y = roof_base;
for (int wz = z0 + 1; wz <= z1 - 1; ++wz) {
for (int wx = x0 + 1; wx <= x1 - 1; ++wx) {
if (wx == ladder_hole_x && wz == ladder_hole_z) continue;
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
set_block_with_height(chunk, lx, lz, attic_y, bp->floor_block);
}
}
if (has_door) {
int step_x = (door_side == 2) ? -1 : (door_side == 3) ? 1 : 0;
int step_z = (door_side == 0) ? -1 : (door_side == 1) ? 1 : 0;
int pad_x = door_x + step_x;
int pad_z = door_z + step_z;
int lx = pad_x - chunk_origin_x;
int lz = pad_z - chunk_origin_z;
if (lx >= 0 && lx < CHUNK_SIZE && lz >= 0 && lz < CHUNK_SIZE) {
set_block_with_height(chunk, lx, lz, base_floor_y, BLOCK_GRAVEL);
}
}
update_columns_for_rect(columns, chunk_origin_x, chunk_origin_z, x0, x1, z0, z1, roof_base + 2);
}
static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *chunk, column_data columns[CHUNK_SIZE][CHUNK_SIZE],
unsigned char occupancy[CHUNK_SIZE][CHUNK_SIZE],
int center_x, int center_z, const cabin_blueprint *bp, rng_state *rng) {
int chunk_origin_x = chunk_x * CHUNK_SIZE;
int chunk_origin_z = chunk_z * CHUNK_SIZE;
if (bp->rect_count == 0 || bp->rect_count > CABIN_MAX_RECTS) return 0;
int rect_center_x[CABIN_MAX_RECTS];
int rect_center_z[CABIN_MAX_RECTS];
int rect_x0[CABIN_MAX_RECTS];
int rect_x1[CABIN_MAX_RECTS];
int rect_z0[CABIN_MAX_RECTS];
int rect_z1[CABIN_MAX_RECTS];
int min_h = INT_MAX;
int max_h = INT_MIN;
for (size_t i = 0; i < bp->rect_count; ++i) {
const cabin_rect *rect = &bp->rects[i];
int rcx = center_x + rect->offset_x;
int rcz = center_z + rect->offset_z;
int x0 = rcx - rect->half_w;
int x1 = rcx + rect->half_w;
int z0 = rcz - rect->half_l;
int z1 = rcz + rect->half_l;
if (x0 < chunk_origin_x + 1 || x1 > chunk_origin_x + CHUNK_SIZE - 2) return 0;
if (z0 < chunk_origin_z + 1 || z1 > chunk_origin_z + CHUNK_SIZE - 2) return 0;
rect_center_x[i] = rcx;
rect_center_z[i] = rcz;
rect_x0[i] = x0;
rect_x1[i] = x1;
rect_z0[i] = z0;
rect_z1[i] = z1;
if (rect_overlaps_mask(occupancy, chunk_origin_x, chunk_origin_z, x0, x1, z0, z1, 3)) {
return 0;
}
for (int wz = z0; wz <= z1; ++wz) {
for (int wx = x0; wx <= x1; ++wx) {
int lx = wx - chunk_origin_x;
int lz = wz - chunk_origin_z;
column_data col = columns[lx][lz];
if (col.has_water && col.height < col.water_surface) return 0;
if (col.height < min_h) min_h = col.height;
if (col.height > max_h) max_h = col.height;
}
}
}
if (min_h == INT_MAX) return 0;
if (max_h - min_h > 3) return 0;
int base_floor_y = min_h + 1;
if (base_floor_y + bp->wall_height * bp->stories + 6 >= CHUNK_HEIGHT) return 0;
int door_rect_index = clamp_int(bp->door_rect_index, 0, (int)bp->rect_count - 1);
int door_side = rng_range_inclusive(rng, 0, 3);
int door_x = rect_center_x[door_rect_index];
int door_z = rect_z0[door_rect_index];
if (door_side == 1) door_z = rect_z1[door_rect_index];
if (door_side == 2) {
door_x = rect_x0[door_rect_index];
door_z = rect_center_z[door_rect_index];
} else if (door_side == 3) {
door_x = rect_x1[door_rect_index];
door_z = rect_center_z[door_rect_index];
}
int door_offset = rng_range_inclusive(rng, -1, 1);
int door_margin = 2;
if (door_side == 0 || door_side == 1) {
int min_door_x = rect_x0[door_rect_index] + door_margin;
int max_door_x = rect_x1[door_rect_index] - door_margin;
if (min_door_x > max_door_x) {
min_door_x = rect_x0[door_rect_index] + 1;
max_door_x = rect_x1[door_rect_index] - 1;
}
door_x = clamp_int(door_x + door_offset, min_door_x, max_door_x);
} else {
int min_door_z = rect_z0[door_rect_index] + door_margin;
int max_door_z = rect_z1[door_rect_index] - door_margin;
if (min_door_z > max_door_z) {
min_door_z = rect_z0[door_rect_index] + 1;
max_door_z = rect_z1[door_rect_index] - 1;
}
door_z = clamp_int(door_z + door_offset, min_door_z, max_door_z);
}
for (size_t i = 0; i < bp->rect_count; ++i) {
int has_door = (int)i == door_rect_index;
build_cabin_rect(ctx, bp, &bp->rects[i], chunk_x, chunk_z, chunk, columns,
rect_center_x[i], rect_center_z[i], base_floor_y,
door_x, door_z, door_side, has_door, rng);
mark_rect_occupancy(occupancy, chunk_origin_x, chunk_origin_z, rect_x0[i], rect_x1[i], rect_z0[i], rect_z1[i], 3);
}
connect_cabin_to_trail(ctx, chunk_x, chunk_z, columns, chunk, door_x, door_z, door_side, bp->path_width);
return 1;
}
static void generate_chunk_cabins(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out) {
uint64_t seed = (uint64_t)ctx->world_seed * 6364136223846793005ULL + (uint64_t)chunk_x * 374761393ULL + (uint64_t)chunk_z * 668265263ULL;
rng_state rng;
rng_seed(&rng, seed ^ 0xCAB1A5u);
double noise = simplex_noise2(&ctx->noise, (chunk_x * CHUNK_SIZE + 5000) * 0.0006, (chunk_z * CHUNK_SIZE - 5000) * 0.0006) * 0.5 + 0.5;
double spawn_chance = 0.015 + noise * 0.02;
if (rng_next_f64(&rng) > spawn_chance) return;
size_t blueprint_count = sizeof(CABIN_BLUEPRINTS) / sizeof(CABIN_BLUEPRINTS[0]);
unsigned char occupancy[CHUNK_SIZE][CHUNK_SIZE];
memset(occupancy, 0, sizeof(occupancy));
int attempts = 1 + (rng_next_f64(&rng) < 0.2 ? 1 : 0);
while (attempts-- > 0) {
const cabin_blueprint *bp = &CABIN_BLUEPRINTS[rng_range_inclusive(&rng, 0, (int)blueprint_count - 1)];
int min_local_x = 2;
int max_local_x = CHUNK_SIZE - 3;
int min_local_z = 2;
int max_local_z = CHUNK_SIZE - 3;
for (size_t i = 0; i < bp->rect_count; ++i) {
const cabin_rect *rect = &bp->rects[i];
int req_min_x = rect->half_w - rect->offset_x + 1;
int req_max_x = CHUNK_SIZE - 2 - (rect->half_w + rect->offset_x);
int req_min_z = rect->half_l - rect->offset_z + 1;
int req_max_z = CHUNK_SIZE - 2 - (rect->half_l + rect->offset_z);
if (req_min_x > min_local_x) min_local_x = req_min_x;
if (req_max_x < max_local_x) max_local_x = req_max_x;
if (req_min_z > min_local_z) min_local_z = req_min_z;
if (req_max_z < max_local_z) max_local_z = req_max_z;
}
if (min_local_x >= max_local_x - 1 || min_local_z >= max_local_z - 1) continue;
int local_cx = rng_range_inclusive(&rng, min_local_x, max_local_x);
int local_cz = rng_range_inclusive(&rng, min_local_z, max_local_z);
int world_cx = chunk_x * CHUNK_SIZE + local_cx;
int world_cz = chunk_z * CHUNK_SIZE + local_cz;
if (try_place_cabin(ctx, chunk_x, chunk_z, out, columns, occupancy, world_cx, world_cz, bp, &rng)) {
break;
}
}
}
static int find_nearest_trail_block(column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *chunk, int chunk_x, int chunk_z,
int start_x, int start_z, int search_radius, int *out_x, int *out_z) {
int chunk_origin_x = chunk_x * CHUNK_SIZE;
int chunk_origin_z = chunk_z * CHUNK_SIZE;
int local_start_x = start_x - chunk_origin_x;
int local_start_z = start_z - chunk_origin_z;
if (local_start_x < 0 || local_start_x >= CHUNK_SIZE || local_start_z < 0 || local_start_z >= CHUNK_SIZE) return 0;
for (int radius = 2; radius <= search_radius; ++radius) {
for (int dz = -radius; dz <= radius; ++dz) {
for (int dx = -radius; dx <= radius; ++dx) {
if (abs(dx) != radius && abs(dz) != radius) continue;
int world_x = start_x + dx;
int world_z = start_z + dz;
int lx = world_x - chunk_origin_x;
int lz = world_z - chunk_origin_z;
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
int column_height = columns[lx][lz].height;
if (column_height < 0 || column_height >= CHUNK_HEIGHT) continue;
uint16_t block = chunk->blocks[column_height][lx][lz];
if (block == BLOCK_GRAVEL) {
*out_x = world_x;
*out_z = world_z;
return 1;
}
}
}
}
return 0;
}
static int find_nearest_trail_point_from_segments(worldgen_ctx *ctx, int start_x, int start_z, int *out_x, int *out_z) {
if (!ctx || ctx->trail_segment_count == 0) return 0;
double best = DBL_MAX;
int bx = 0, bz = 0;
for (size_t i = 0; i < ctx->trail_segment_count; ++i) {
trail_segment *seg = &ctx->trail_segments[i];
if (!seg || seg->count < 1 || !seg->points) continue;
for (int p = 0; p < seg->count; ++p) {
int tx = seg->points[p * 2];
int tz = seg->points[p * 2 + 1];
double dx = (double)(tx - start_x);
double dz = (double)(tz - start_z);
double d2 = dx * dx + dz * dz;
if (d2 < best) {
best = d2;
bx = tx;
bz = tz;
}
}
}
if (best == DBL_MAX) return 0;
*out_x = bx;
*out_z = bz;
return 1;
}
static void connect_cabin_to_trail(worldgen_ctx *ctx, int chunk_x, int chunk_z,
column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *chunk,
int door_x, int door_z, int door_side, int path_width) {
int step_x = (door_side == 2) ? -1 : (door_side == 3) ? 1 : 0;
int step_z = (door_side == 0) ? -1 : (door_side == 1) ? 1 : 0;
int start_x = door_x + step_x;
int start_z = door_z + step_z;
int spur_len = 24;
if (path_width < 2) path_width = 2;
carve_cabin_path(ctx, chunk_x, chunk_z, chunk, columns, start_x, start_z, step_x, step_z, spur_len, path_width);
int target_x = 0, target_z = 0;
int found = 0;
if (ctx && ctx->enable_trails) {
found = find_nearest_trail_point_from_segments(ctx, start_x, start_z, &target_x, &target_z);
}
if (!found) {
found = find_nearest_trail_block(columns, chunk, chunk_x, chunk_z, start_x, start_z, 128, &target_x, &target_z);
}
if (!found) {
int fallback_x = start_x + step_x * 80;
int fallback_z = start_z + step_z * 80;
carve_trail_span(ctx, chunk_x, chunk_z, chunk, columns, start_x, start_z, fallback_x, fallback_z, path_width);
return;
}
int *pts = NULL;
int count = 0;
if (build_trail_path(ctx, (double)start_x, (double)start_z, (double)target_x, (double)target_z, &pts, &count) && pts && count >= 2) {
for (int i = 1; i < count; ++i) {
int x0 = pts[(i - 1) * 2];
int z0 = pts[(i - 1) * 2 + 1];
int x1 = pts[i * 2];
int z1 = pts[i * 2 + 1];
carve_trail_span(ctx, chunk_x, chunk_z, chunk, columns, x0, z0, x1, z1, path_width);
}
free(pts);
} else {
carve_trail_span(ctx, chunk_x, chunk_z, chunk, columns, start_x, start_z, target_x, target_z, path_width);
}
}
static uint16_t generate_normal_ores(worldgen_ctx *ctx, int x, int y, int z, const column_data *col) {
uint32_t seed = (uint32_t)ctx->world_seed;
double cluster;
double chance;
double weight;
uint32_t h;
weight = ore_depth_weight(y, 16, 0);
if (weight > 0.0) {
cluster = ore_cluster_field(ctx, x, y, z, 0.032, 19000.0);
if (cluster > 0.78) {
chance = 0.003 + weight * 0.02;
h = hash_coords3(x, y, z, seed ^ 0x0D14F1A3u);
if ((h & 0xFFFF) <= (uint32_t)(chance * 65535.0)) {
return BLOCK_DIAMOND_ORE;
}
}
}
weight = ore_depth_weight(y, 20, 0);
if (weight > 0.0) {
cluster = ore_cluster_field(ctx, x, y, z, 0.028, 12000.0);
if (cluster > 0.65) {
chance = 0.01 + weight * 0.12;
h = hash_coords3(x, y, z, seed ^ 0x0BADC0DEu);
if ((h & 0xFFFF) <= (uint32_t)(chance * 65535.0)) {
return BLOCK_REDSTONE_ORE;
}
}
}
weight = ore_depth_weight(y, 24, 2);
if (weight > 0.0) {
cluster = ore_cluster_field(ctx, x, y, z, 0.026, 15000.0);
if (cluster > 0.67) {
chance = 0.006 + weight * 0.07;
h = hash_coords3(x, y, z, seed ^ 0x1A5150Eu);
if ((h & 0xFFFF) <= (uint32_t)(chance * 65535.0)) {
return BLOCK_LAPIS_ORE;
}
}
}
weight = ore_depth_weight(y, 32, 0);
if (weight > 0.0) {
cluster = ore_cluster_field(ctx, x, y, z, 0.02, 8000.0);
if (cluster > 0.63) {
double altitude = (double)(col->height - ctx->sea_level);
double mountain = clamp01(altitude / 40.0);
chance = (0.008 + weight * 0.06) * (1.0 + mountain * 0.8);
h = hash_coords3(x, y, z, seed ^ 0x90ADCAFEu);
if ((h & 0xFFFF) <= (uint32_t)(chance * 65535.0)) {
return BLOCK_GOLD_ORE;
}
}
}
weight = ore_depth_weight(y, 68, -4);
if (weight > 0.0) {
cluster = ore_cluster_field(ctx, x, y, z, 0.018, 6000.0);
if (cluster > 0.58) {
double biome_bonus = (col->biome == BIOME_EAST_KY_RIDGEBREAKS) ? 0.05 : 0.0;
chance = 0.04 + weight * 0.25 + biome_bonus;
h = hash_coords3(x, y, z, seed ^ 0x1B0EFACEu);
if ((h & 0xFFFF) <= (uint32_t)(chance * 65535.0)) {
return BLOCK_IRON_ORE;
}
}
}
weight = ore_depth_weight(y, 92, 10);
if (weight > 0.0) {
cluster = ore_cluster_field(ctx, x, y, z, 0.015, 4200.0);
if (cluster > 0.62) {
double plains_bonus = (col->biome == BIOME_WEST_KY_COALFIELDS) ? 0.05 : 0.0;
chance = 0.03 + weight * 0.18 + plains_bonus;
h = hash_coords3(x, y, z, seed ^ 0xC0FFEE12u);
if ((h & 0xFFFF) <= (uint32_t)(chance * 65535.0)) {
return BLOCK_COPPER_ORE;
}
}
}
return BLOCK_STONE;
}
static int column_has_manmade_surface(chunk_data *chunk, int chunk_x, int chunk_z, int world_x, int world_z) {
int local_x = world_x - chunk_x * CHUNK_SIZE;
int local_z = world_z - chunk_z * CHUNK_SIZE;
if (local_x < 0 || local_x >= CHUNK_SIZE || local_z < 0 || local_z >= CHUNK_SIZE) return 0;
for (int y = CHUNK_HEIGHT - 2; y >= 1; --y) {
uint16_t block = chunk->blocks[y][local_x][local_z];
if (block == BLOCK_AIR) continue;
if (block == BLOCK_WATER) return 0;
if (block == BLOCK_GRAVEL || block == BLOCK_SPRUCE_PLANKS || block == BLOCK_OAK_PLANKS ||
block == BLOCK_OAK_LOG_X || block == BLOCK_OAK_LOG_Z ||
block == BLOCK_SPRUCE_LOG_X || block == BLOCK_SPRUCE_LOG_Z ||
block == BLOCK_GLASS_PANE || block == BLOCK_SPRUCE_STAIRS_E || block == BLOCK_SPRUCE_STAIRS_W ||
block == BLOCK_LADDER_N || block == BLOCK_LADDER_S || block == BLOCK_LADDER_E || block == BLOCK_LADDER_W ||
block == BLOCK_SPRUCE_DOOR_N_LOWER || block == BLOCK_SPRUCE_DOOR_N_UPPER ||
block == BLOCK_SPRUCE_DOOR_S_LOWER || block == BLOCK_SPRUCE_DOOR_S_UPPER ||
block == BLOCK_SPRUCE_DOOR_E_LOWER || block == BLOCK_SPRUCE_DOOR_E_UPPER ||
block == BLOCK_SPRUCE_DOOR_W_LOWER || block == BLOCK_SPRUCE_DOOR_W_UPPER) {
return 1;
}
return 0;
}
return 0;
}
static void generate_chunk_trees(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *chunk, block_list *out) {
const int grid = 5;
const int margin = 4;
const int min_tree_alt = ctx->sea_level - 2;
const int low_fade_top = ctx->sea_level + 6;
const int tree_line = ctx->sea_level + 60;
const int tree_line_fade = 20;
const int max_slope = 2;
int chunk_min_x = chunk_x * CHUNK_SIZE - margin;
int chunk_max_x = chunk_min_x + CHUNK_SIZE + 2 * margin - 1;
int chunk_min_z = chunk_z * CHUNK_SIZE - margin;
int chunk_max_z = chunk_min_z + CHUNK_SIZE + 2 * margin - 1;
int grid_min_x = (int)floor((double)chunk_min_x / grid);
int grid_max_x = (int)floor((double)chunk_max_x / grid);
int grid_min_z = (int)floor((double)chunk_min_z / grid);
int grid_max_z = (int)floor((double)chunk_max_z / grid);
for (int gx = grid_min_x; gx <= grid_max_x; ++gx) {
for (int gz = grid_min_z; gz <= grid_max_z; ++gz) {
uint64_t seed = (uint64_t)ctx->world_seed + (uint64_t)gx * 341873128712ULL + (uint64_t)gz * 132897987541ULL;
rng_state rng;
rng_seed(&rng, seed);
int candidate_x = gx * grid + rng_range_inclusive(&rng, 0, grid - 1);
int candidate_z = gz * grid + rng_range_inclusive(&rng, 0, grid - 1);
if (candidate_x < chunk_min_x || candidate_x > chunk_max_x || candidate_z < chunk_min_z || candidate_z > chunk_max_z) {
continue;
}
column_data data = get_column_data(ctx, candidate_x, candidate_z);
if (data.has_water && data.height < data.water_surface) continue;
if (data.height < min_tree_alt || data.height > tree_line) continue;
if (ground_slope(ctx, candidate_x, candidate_z) > max_slope) continue;
int is_old_growth = (data.biome == BIOME_OLD_GROWTH_PLAINS);
double grove_mask = is_old_growth ? old_growth_grove_mask(ctx, candidate_x, candidate_z) : 0.0;
if (is_old_growth && grove_mask < 0.35) {
double skip_chance = 0.7 - grove_mask * 0.5;
if (rng_next_f64(&rng) < skip_chance) continue;
}
double altitude_factor = 1.0;
if (data.height < low_fade_top) {
int span = low_fade_top - min_tree_alt;
if (span < 1) span = 1;
altitude_factor *= clamp01((double)(data.height - min_tree_alt) / span);
}
if (data.height > tree_line - tree_line_fade) {
altitude_factor *= clamp01((double)(tree_line - data.height) / tree_line_fade);
}
if (is_old_growth) {
altitude_factor *= 0.85 + 0.15 * grove_mask;
}
if (altitude_factor <= 0.0) continue;
double density = tree_density_mask(ctx, candidate_x, candidate_z);
if (is_old_growth) {
density = clamp01(0.25 + grove_mask * 0.75);
}
double spawn_prob = 0.7 * (0.55 + 0.45 * density) * altitude_factor;
if (is_old_growth) {
spawn_prob = clamp01(0.25 + grove_mask * 0.7) * altitude_factor;
}
if (rng_next_f64(&rng) > spawn_prob) continue;
block_list tmp;
block_list_init(&tmp);
const tree_archetype *arch = choose_tree_archetype(ctx, &data, &rng);
int base_y = data.height + 1;
uint16_t surface = select_surface_block(ctx, &data, candidate_x, candidate_z);
if (surface == BLOCK_SNOW) {
base_y = data.height;
}
if (column_has_manmade_surface(chunk, chunk_x, chunk_z, candidate_x, candidate_z)) {
block_list_free(&tmp);
continue;
}
generate_tree(ctx, arch, candidate_x, base_y, candidate_z, &rng, &tmp);
if (tmp.count == 0) {
block_list_free(&tmp);
continue;
}
for (size_t i = 0; i < tmp.count; ++i) {
int bx = tmp.items[i].x;
int by = tmp.items[i].y;
int bz = tmp.items[i].z;
if (by < 0 || by >= CHUNK_HEIGHT) continue;
if (bx >= chunk_x * CHUNK_SIZE && bx <= chunk_x * CHUNK_SIZE + CHUNK_SIZE - 1 &&
bz >= chunk_z * CHUNK_SIZE && bz <= chunk_z * CHUNK_SIZE + CHUNK_SIZE - 1) {
block_list_push(out, bx, by, bz, tmp.items[i].id);
}
}
block_list_free(&tmp);
}
}
}
static void trail_node_position(worldgen_ctx *ctx, int node_x, int node_z, double spacing, double *out_x, double *out_z) {
double base_x = node_x * spacing;
double base_z = node_z * spacing;
double jitter = spacing * 0.4;
double jx = simplex_noise2(&ctx->noise, node_x * 0.11, node_z * 0.11) * jitter;
double jz = simplex_noise2(&ctx->noise, (node_x + 4000) * 0.11, (node_z - 4000) * 0.11) * jitter;
*out_x = base_x + jx;
*out_z = base_z + jz;
}
typedef struct {
int dx;
int dz;
} trail_neighbor_offset;
static const trail_neighbor_offset TRAIL_NEIGHBOR_OFFSETS[] = {
{ 1, 0 },
{ 0, 1 },
{ 1, 1 },
{ 1, -1 },
{ 2, 0 },
{ 0, 2 },
{ 2, 1 },
{ 1, 2 }
};
static uint32_t trail_segment_hash(int ax, int az, int bx, int bz, uint32_t seed) {
if (bx < ax || (bx == ax && bz < az)) {
int tx = ax;
int tz = az;
ax = bx;
az = bz;
bx = tx;
bz = tz;
}
int mix_x = ax * 73856093 + bx * 19349663;
int mix_z = az * 83492791 + bz * 29791;
return hash_coords(mix_x, mix_z, seed);
}
static int should_connect_trail_nodes(worldgen_ctx *ctx, int node_x0, int node_z0, int node_x1, int node_z1) {
if (node_x0 == node_x1 && node_z0 == node_z1) return 0;
uint32_t seg_hash = trail_segment_hash(node_x0, node_z0, node_x1, node_z1, (uint32_t)ctx->world_seed ^ 0xB37D4A91u);
double ax, az, bx, bz;
trail_node_position(ctx, node_x0, node_z0, TRAIL_NODE_SPACING, &ax, &az);
trail_node_position(ctx, node_x1, node_z1, TRAIL_NODE_SPACING, &bx, &bz);
double mid_x = 0.5 * (ax + bx);
double mid_z = 0.5 * (az + bz);
int mid_ix = (int)llround(mid_x);
int mid_iz = (int)llround(mid_z);
column_data mid_column = get_column_data(ctx, mid_ix, mid_iz);
double ridge_bias = clamp01((double)(mid_column.height - ctx->sea_level) / 40.0);
if (mid_column.has_water && mid_column.height <= mid_column.water_surface) {
ridge_bias -= 0.35;
}
double dir_x = bx - ax;
double dir_z = bz - az;
double dir_len = sqrt(dir_x * dir_x + dir_z * dir_z);
if (dir_len > 0.0001) {
dir_x /= dir_len;
dir_z /= dir_len;
}
double flow_x = simplex_noise2(&ctx->noise, mid_x * 0.0005, mid_z * 0.0005);
double flow_z = simplex_noise2(&ctx->noise, (mid_x + 2000) * 0.0005, (mid_z - 2000) * 0.0005);
double flow_len = sqrt(flow_x * flow_x + flow_z * flow_z);
double alignment = 0.0;
if (flow_len > 0.0001) {
flow_x /= flow_len;
flow_z /= flow_len;
alignment = fabs(dir_x * flow_x + dir_z * flow_z);
}
double dx = fabs((double)(node_x1 - node_x0));
double dz = fabs((double)(node_z1 - node_z0));
double grid_len = sqrt(dx * dx + dz * dz);
double base = 0.12;
if (grid_len <= 1.05) {
base += 0.2;
} else if (grid_len <= 2.2) {
base += 0.12;
} else {
base += 0.05;
}
int axis_edge = (dx == 0.0 || dz == 0.0);
if (axis_edge) {
double corridor = simplex_noise2(&ctx->noise, (mid_x + 50000.0) * 0.0007, (mid_z - 50000.0) * 0.0007) * 0.5 + 0.5;
double penalty = 0.22 + corridor * 0.3;
base -= penalty;
if (grid_len <= 1.05) {
double axis_gate = 0.22 + corridor * 0.35;
double axis_rand = ((seg_hash >> 8) & 0xFFFF) / 65535.0;
if (axis_rand > axis_gate) {
return 0;
}
}
} else if (dx == dz && dx > 0.1) {
base += 0.07;
}
base += (alignment - 0.5) * 0.2;
base += (ridge_bias - 0.5) * 0.15;
double texture = simplex_noise2(&ctx->noise, mid_x * 0.0012, mid_z * 0.0012) * 0.5 + 0.5;
base += (texture - 0.5) * 0.15;
if (base < 0.02) base = 0.02;
if (base > 0.85) base = 0.85;
double r = (seg_hash + 1.0) / 4294967296.0;
return r < base;
}
static int build_trail_path(worldgen_ctx *ctx, double ax, double az, double bx, double bz, int **out_points, int *out_count) {
double min_x = fmin(ax, bx) - TRAIL_MARGIN;
double max_x = fmax(ax, bx) + TRAIL_MARGIN;
double min_z = fmin(az, bz) - TRAIL_MARGIN;
double max_z = fmax(az, bz) + TRAIL_MARGIN;
int grid_w = (int)((max_x - min_x) / TRAIL_CELL_SIZE) + 2;
int grid_h = (int)((max_z - min_z) / TRAIL_CELL_SIZE) + 2;
if (grid_w < 2) grid_w = 2;
if (grid_h < 2) grid_h = 2;
int total = grid_w * grid_h;
double *heights = (double *)malloc((size_t)total * sizeof(double));
if (!heights) return 0;
for (int gz = 0; gz < grid_h; ++gz) {
double wz = min_z + gz * TRAIL_CELL_SIZE;
for (int gx = 0; gx < grid_w; ++gx) {
double wx = min_x + gx * TRAIL_CELL_SIZE;
heights[gz * grid_w + gx] = column_height(ctx, (int)llround(wx), (int)llround(wz));
}
}
int sx = (int)llround((ax - min_x) / TRAIL_CELL_SIZE);
int sz = (int)llround((az - min_z) / TRAIL_CELL_SIZE);
int tx = (int)llround((bx - min_x) / TRAIL_CELL_SIZE);
int tz = (int)llround((bz - min_z) / TRAIL_CELL_SIZE);
if (sx < 0) sx = 0;
if (sx >= grid_w) sx = grid_w - 1;
if (sz < 0) sz = 0;
if (sz >= grid_h) sz = grid_h - 1;
if (tx < 0) tx = 0;
if (tx >= grid_w) tx = grid_w - 1;
if (tz < 0) tz = 0;
if (tz >= grid_h) tz = grid_h - 1;
int start = sz * grid_w + sx;
int goal = tz * grid_w + tx;
double *dist = (double *)malloc((size_t)total * sizeof(double));
int *prev = (int *)malloc((size_t)total * sizeof(int));
unsigned char *used = (unsigned char *)calloc((size_t)total, sizeof(unsigned char));
if (!dist || !prev || !used) {
free(heights);
free(dist);
free(prev);
free(used);
return 0;
}
for (int i = 0; i < total; ++i) {
dist[i] = DBL_MAX;
prev[i] = -1;
}
dist[start] = 0.0;
int neighbors[8][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}};
for (int step = 0; step < total; ++step) {
int current = -1;
double best = DBL_MAX;
for (int i = 0; i < total; ++i) {
if (!used[i] && dist[i] < best) {
best = dist[i];
current = i;
}
}
if (current == -1 || current == goal) break;
used[current] = 1;
int cx = current % grid_w;
int cz = current / grid_w;
double current_height = heights[current];
for (int n = 0; n < 8; ++n) {
int nx = cx + neighbors[n][0];
int nz = cz + neighbors[n][1];
if (nx < 0 || nx >= grid_w || nz < 0 || nz >= grid_h) continue;
int ni = nz * grid_w + nx;
if (used[ni]) continue;
double next_height = heights[ni];
double slope = fabs(next_height - current_height);
if (slope > 3.0) continue;
int water = 0;
int wx = (int)llround(min_x + nx * TRAIL_CELL_SIZE);
int wz = (int)llround(min_z + nz * TRAIL_CELL_SIZE);
column_data cd = get_column_data(ctx, wx, wz);
if (cd.has_water && next_height <= cd.water_surface) {
water = 1;
}
double base_cost = (neighbors[n][0] == 0 || neighbors[n][1] == 0) ? 1.0 : 1.41421356237;
double cost = base_cost * (1.0 + slope * 6.0);
if (water) {
cost += 10.0;
}
if (dist[current] + cost < dist[ni]) {
dist[ni] = dist[current] + cost;
prev[ni] = current;
}
}
}
int path_len = 0;
for (int cur = goal; cur != -1; cur = prev[cur]) {
path_len++;
if (cur == start) break;
}
if (path_len < 2) {
free(heights);
free(dist);
free(prev);
free(used);
return 0;
}
int *points = (int *)malloc((size_t)path_len * 2 * sizeof(int));
if (!points) {
free(heights);
free(dist);
free(prev);
free(used);
return 0;
}
int cur = goal;
for (int i = path_len - 1; i >= 0; --i) {
int gx = cur % grid_w;
int gz = cur / grid_w;
points[i * 2] = (int)llround(min_x + gx * TRAIL_CELL_SIZE);
points[i * 2 + 1] = (int)llround(min_z + gz * TRAIL_CELL_SIZE);
cur = prev[cur];
if (cur == -1 && i > 0) {
points[(i - 1) * 2] = points[i * 2];
points[(i - 1) * 2 + 1] = points[i * 2 + 1];
break;
}
}
*out_points = points;
*out_count = path_len;
free(heights);
free(dist);
free(prev);
free(used);
return 1;
}
static trail_segment *get_trail_segment(worldgen_ctx *ctx, int node_x0, int node_z0, int node_x1, int node_z1) {
if (node_x0 == node_x1 && node_z0 == node_z1) return NULL;
if (node_x1 < node_x0 || (node_x1 == node_x0 && node_z1 < node_z0)) {
int tx = node_x0, tz = node_z0;
node_x0 = node_x1;
node_z0 = node_z1;
node_x1 = tx;
node_z1 = tz;
}
for (size_t i = 0; i < ctx->trail_segment_count; ++i) {
trail_segment *seg = &ctx->trail_segments[i];
if (seg->ax == node_x0 && seg->az == node_z0 && seg->bx == node_x1 && seg->bz == node_z1) {
return seg;
}
}
if (ctx->trail_segment_count >= ctx->trail_segment_cap) {
size_t new_cap = ctx->trail_segment_cap ? ctx->trail_segment_cap * 2 : 16;
trail_segment *resized = (trail_segment *)realloc(ctx->trail_segments, new_cap * sizeof(trail_segment));
if (!resized) return NULL;
ctx->trail_segments = resized;
ctx->trail_segment_cap = new_cap;
}
trail_segment *seg = &ctx->trail_segments[ctx->trail_segment_count];
memset(seg, 0, sizeof(*seg));
seg->ax = node_x0;
seg->az = node_z0;
seg->bx = node_x1;
seg->bz = node_z1;
double ax, az, bx, bz;
trail_node_position(ctx, node_x0, node_z0, TRAIL_NODE_SPACING, &ax, &az);
trail_node_position(ctx, node_x1, node_z1, TRAIL_NODE_SPACING, &bx, &bz);
int *points = NULL;
int count = 0;
if (!build_trail_path(ctx, ax, az, bx, bz, &points, &count)) {
points = (int *)malloc(4 * sizeof(int));
if (!points) return NULL;
points[0] = (int)llround(ax);
points[1] = (int)llround(az);
points[2] = (int)llround(bx);
points[3] = (int)llround(bz);
count = 2;
}
seg->points = points;
seg->count = count;
ctx->trail_segment_count++;
return seg;
}
static void place_trail_column(chunk_data *out, column_data columns[CHUNK_SIZE][CHUNK_SIZE], int chunk_x, int chunk_z, int world_x, int world_z, int target_height) {
int local_x = world_x - chunk_x * CHUNK_SIZE;
int local_z = world_z - chunk_z * CHUNK_SIZE;
if (local_x < 0 || local_x >= CHUNK_SIZE || local_z < 0 || local_z >= CHUNK_SIZE) return;
if (target_height < 1) target_height = 1;
if (target_height >= CHUNK_HEIGHT - 2) target_height = CHUNK_HEIGHT - 3;
int original_height = columns[local_x][local_z].height;
if (original_height < 1) original_height = target_height;
for (int y = target_height - 1; y >= 1; --y) {
uint16_t block = out->blocks[y][local_x][local_z];
if (block == BLOCK_AIR || block == BLOCK_WATER) {
out->blocks[y][local_x][local_z] = BLOCK_DIRT;
} else {
break;
}
}
if (target_height > original_height) {
for (int y = original_height + 1; y <= target_height && y < CHUNK_HEIGHT; ++y) {
uint16_t fill = (y == target_height) ? BLOCK_GRAVEL : BLOCK_DIRT;
out->blocks[y][local_x][local_z] = fill;
}
} else {
out->blocks[target_height][local_x][local_z] = BLOCK_GRAVEL;
for (int y = target_height + 1; y <= original_height && y < CHUNK_HEIGHT; ++y) {
if (y <= target_height + 3) {
out->blocks[y][local_x][local_z] = BLOCK_AIR;
} else {
break;
}
}
}
columns[local_x][local_z].height = target_height;
}
static void carve_trail_pad(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *out, column_data columns[CHUNK_SIZE][CHUNK_SIZE], int center_x, int center_z, int radius, int target_height) {
if (radius < 1) radius = 1;
(void)ctx;
int radius_sq = radius * radius;
for (int dz = -radius; dz <= radius; ++dz) {
for (int dx = -radius; dx <= radius; ++dx) {
if (dx * dx + dz * dz > radius_sq) continue;
int wx = center_x + dx;
int wz = center_z + dz;
place_trail_column(out, columns, chunk_x, chunk_z, wx, wz, target_height);
}
}
}
static void carve_trail_span(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *out, column_data columns[CHUNK_SIZE][CHUNK_SIZE], int x0, int z0, int x1, int z1, int width) {
double tx = (double)(x1 - x0);
double tz = (double)(z1 - z0);
double len = sqrt(tx * tx + tz * tz);
const int max_step_up = 1;
const int max_step_down = 1;
const double carry_weight = 0.65;
if (len < 0.0001) {
column_data data = get_column_data(ctx, x0, z0);
carve_trail_pad(ctx, chunk_x, chunk_z, out, columns, x0, z0, width - 1, data.height);
return;
}
int steps = (int)ceil(len / 0.5);
if (steps < 1) steps = 1;
double nx = -tz / len;
double nz = tx / len;
int last_height = INT32_MIN;
int start_height = INT32_MIN;
double half_width = (double)width / 2.0;
for (int i = 0; i <= steps; ++i) {
double t = (double)i / (double)steps;
double px = x0 + tx * t;
double pz = z0 + tz * t;
int wx = (int)llround(px);
int wz = (int)llround(pz);
column_data data = get_column_data(ctx, wx, wz);
int target = data.height;
if (last_height != INT32_MIN) {
double blended = carry_weight * (double)last_height + (1.0 - carry_weight) * (double)target;
target = (int)llround(blended);
if (target > last_height + max_step_up) {
target = last_height + max_step_up;
} else if (target < last_height - max_step_down) {
target = last_height - max_step_down;
}
} else {
start_height = target;
}
last_height = target;
for (double w = -half_width; w <= half_width + 0.01; w += 0.5) {
double ox = px + nx * w;
double oz = pz + nz * w;
int fx = (int)llround(ox);
int fz = (int)llround(oz);
place_trail_column(out, columns, chunk_x, chunk_z, fx, fz, target);
}
}
int pad_radius = width - 1;
int dx = x1 - x0;
int dz = z1 - z0;
int use_pads = (abs(dx) > 0 && abs(dz) > 0);
if (use_pads && start_height != INT32_MIN) {
carve_trail_pad(ctx, chunk_x, chunk_z, out, columns, x0, z0, pad_radius, start_height);
}
if (use_pads && last_height != INT32_MIN) {
carve_trail_pad(ctx, chunk_x, chunk_z, out, columns, x1, z1, pad_radius, last_height);
}
}
static void generate_chunk_trails(worldgen_ctx *ctx, int chunk_x, int chunk_z, column_data columns[CHUNK_SIZE][CHUNK_SIZE], chunk_data *out) {
int chunk_min_x = chunk_x * CHUNK_SIZE;
int chunk_max_x = chunk_min_x + CHUNK_SIZE - 1;
int chunk_min_z = chunk_z * CHUNK_SIZE;
int chunk_max_z = chunk_min_z + CHUNK_SIZE - 1;
int width = TRAIL_WIDTH;
if (ctx->trail_segment_count > 0) {
for (size_t s = 0; s < ctx->trail_segment_count; ++s) {
trail_segment *seg = &ctx->trail_segments[s];
if (!seg || seg->count < 2) continue;
for (int i = 1; i < seg->count; ++i) {
int x0 = seg->points[(i - 1) * 2];
int z0 = seg->points[(i - 1) * 2 + 1];
int x1 = seg->points[i * 2];
int z1 = seg->points[i * 2 + 1];
int span_min_x = (x0 < x1) ? x0 : x1;
int span_max_x = (x0 > x1) ? x0 : x1;
int span_min_z = (z0 < z1) ? z0 : z1;
int span_max_z = (z0 > z1) ? z0 : z1;
if (span_max_x < chunk_min_x - 4 || span_min_x > chunk_max_x + 4) continue;
if (span_max_z < chunk_min_z - 4 || span_min_z > chunk_max_z + 4) continue;
carve_trail_span(ctx, chunk_x, chunk_z, out, columns, x0, z0, x1, z1, width);
}
}
return;
}
const double spacing = TRAIL_NODE_SPACING;
int min_node_x = (int)floor((chunk_min_x - spacing) / spacing);
int max_node_x = (int)ceil((chunk_max_x + spacing) / spacing);
int min_node_z = (int)floor((chunk_min_z - spacing) / spacing);
int max_node_z = (int)ceil((chunk_max_z + spacing) / spacing);
size_t neighbor_count = sizeof(TRAIL_NEIGHBOR_OFFSETS) / sizeof(TRAIL_NEIGHBOR_OFFSETS[0]);
for (int nx = min_node_x; nx <= max_node_x; ++nx) {
for (int nz = min_node_z; nz <= max_node_z; ++nz) {
for (size_t n = 0; n < neighbor_count; ++n) {
int nnx = nx + TRAIL_NEIGHBOR_OFFSETS[n].dx;
int nnz = nz + TRAIL_NEIGHBOR_OFFSETS[n].dz;
if (!should_connect_trail_nodes(ctx, nx, nz, nnx, nnz)) continue;
trail_segment *seg = get_trail_segment(ctx, nx, nz, nnx, nnz);
if (!seg || seg->count < 2) continue;
for (int i = 1; i < seg->count; ++i) {
int x0 = seg->points[(i - 1) * 2];
int z0 = seg->points[(i - 1) * 2 + 1];
int x1 = seg->points[i * 2];
int z1 = seg->points[i * 2 + 1];
int span_min_x = (x0 < x1) ? x0 : x1;
int span_max_x = (x0 > x1) ? x0 : x1;
int span_min_z = (z0 < z1) ? z0 : z1;
int span_max_z = (z0 > z1) ? z0 : z1;
if (span_max_x < chunk_min_x - 4 || span_min_x > chunk_max_x + 4) continue;
if (span_max_z < chunk_min_z - 4 || span_min_z > chunk_max_z + 4) continue;
carve_trail_span(ctx, chunk_x, chunk_z, out, columns, x0, z0, x1, z1, width);
}
}
}
}
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
void worldgen_init(worldgen_ctx *ctx, int world_seed, int sea_level, int snow_line) {
ctx->world_seed = world_seed;
ctx->sea_level = sea_level;
ctx->snow_line = snow_line;
ctx->enable_trails = 0;
ctx->trail_segments = NULL;
ctx->trail_segment_count = 0;
ctx->trail_segment_cap = 0;
ctx->prepass_done = 0;
ctx->prepass_min_x = ctx->prepass_max_x = 0;
ctx->prepass_min_z = ctx->prepass_max_z = 0;
simplex_init(&ctx->noise, (uint32_t)world_seed);
}
void worldgen_generate_chunk(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *out) {
memset(out, 0, sizeof(*out));
out->chunk_x = chunk_x;
out->chunk_z = chunk_z;
ensure_trail_prepass(ctx, chunk_x, chunk_z);
// Precompute column data for base terrain
column_data columns[CHUNK_SIZE][CHUNK_SIZE];
for (int dx = 0; dx < CHUNK_SIZE; ++dx) {
for (int dz = 0; dz < CHUNK_SIZE; ++dz) {
int gx = chunk_x * CHUNK_SIZE + dx;
int gz = chunk_z * CHUNK_SIZE + dz;
columns[dx][dz] = get_column_data(ctx, gx, gz);
out->heightmap[dx][dz] = (uint16_t)columns[dx][dz].height;
}
}
// Fill base terrain
for (int dx = 0; dx < CHUNK_SIZE; ++dx) {
for (int dz = 0; dz < CHUNK_SIZE; ++dz) {
column_data cd = columns[dx][dz];
int world_x = chunk_x * CHUNK_SIZE + dx;
int world_z = chunk_z * CHUNK_SIZE + dz;
for (int y = 0; y < CHUNK_HEIGHT; ++y) {
int id;
if (y == 0) {
id = BLOCK_BEDROCK;
} else if (y < cd.height - 3) {
if (generate_coal(ctx, world_x, y, world_z, cd.height, cd.biome)) {
id = BLOCK_COAL;
} else {
id = (int)generate_normal_ores(ctx, world_x, y, world_z, &cd);
}
} else if (y < cd.height) {
id = BLOCK_DIRT;
} else if (y == cd.height) {
id = select_surface_block(ctx, &cd, world_x, world_z);
} else if (cd.has_water && y <= cd.water_surface && y > cd.height) {
id = BLOCK_WATER;
} else {
id = BLOCK_AIR;
}
out->blocks[y][dx][dz] = (uint16_t)id;
}
}
}
if (ctx->enable_trails) {
generate_chunk_trails(ctx, chunk_x, chunk_z, columns, out);
}
generate_chunk_cabins(ctx, chunk_x, chunk_z, columns, out);
generate_chunk_grass(ctx, chunk_x, chunk_z, columns, out);
generate_chunk_flowers(ctx, chunk_x, chunk_z, columns, out);
// Tree overlay
block_list trees;
block_list_init(&trees);
generate_chunk_trees(ctx, chunk_x, chunk_z, out, &trees);
for (size_t i = 0; i < trees.count; ++i) {
int lx = trees.items[i].x - chunk_x * CHUNK_SIZE;
int lz = trees.items[i].z - chunk_z * CHUNK_SIZE;
int ly = trees.items[i].y;
if (lx >= 0 && lx < CHUNK_SIZE && lz >= 0 && lz < CHUNK_SIZE && ly >= 0 && ly < CHUNK_HEIGHT) {
out->blocks[ly][lx][lz] = trees.items[i].id;
}
}
block_list_free(&trees);
}