#include "worldgen.h" #include #include #include #include #include #include #include #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); }