|
|
|
|
@@ -10,7 +10,8 @@
|
|
|
|
|
|
|
|
|
|
#define TRAIL_NODE_SPACING 1600.0
|
|
|
|
|
#define TRAIL_CELL_SIZE 16.0
|
|
|
|
|
#define TRAIL_MARGIN 96.0
|
|
|
|
|
#define TRAIL_MARGIN 256.0
|
|
|
|
|
#define TRAIL_LANDFORM_RADIUS 64
|
|
|
|
|
#define TRAIL_WIDTH 5
|
|
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
|
@@ -50,6 +51,7 @@ typedef struct trail_segment {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int column_height(worldgen_ctx *ctx, int x, int z);
|
|
|
|
|
static double worley_distance(int x, int z, double scale, uint32_t seed);
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// RNG (deterministic, cheap)
|
|
|
|
|
@@ -215,6 +217,46 @@ static double land_value(worldgen_ctx *ctx, int x, int z) {
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Broad, seed-stable pockets where cabins form recognizable communities.
|
|
|
|
|
* Roads and cabin spawning share this field, so bridge permissions occur in
|
|
|
|
|
* the same woods where the extra local traffic is actually generated. */
|
|
|
|
|
static double woodland_community_strength(worldgen_ctx *ctx, int x, int z) {
|
|
|
|
|
double cell = worley_distance(x + 18000, z - 26000, 0.00105,
|
|
|
|
|
0xC011AB1Eu ^ (uint32_t)ctx->world_seed);
|
|
|
|
|
double cluster = smoothstep01((0.62 - cell) / 0.32);
|
|
|
|
|
double texture = simplex_noise2(&ctx->noise, (x - 31000) * 0.0018,
|
|
|
|
|
(z + 22000) * 0.0018) * 0.5 + 0.5;
|
|
|
|
|
return clamp01(cluster * 0.72 + texture * 0.28);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int is_dry_sea_level_bank(worldgen_ctx *ctx, int x, int z) {
|
|
|
|
|
column_data bank = get_column_data(ctx, x, z);
|
|
|
|
|
if (bank.has_water && bank.height < bank.water_surface) return 0;
|
|
|
|
|
return bank.height >= ctx->sea_level - 1 && bank.height <= ctx->sea_level + 6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int small_community_water_crossing(worldgen_ctx *ctx, int x, int z, int dir_x, int dir_z) {
|
|
|
|
|
column_data water = get_column_data(ctx, x, z);
|
|
|
|
|
if (!water.has_water || water.water_surface != ctx->sea_level || water.height >= water.water_surface) return 0;
|
|
|
|
|
if (water.water_surface - water.height > 6) return 0;
|
|
|
|
|
if (woodland_community_strength(ctx, x, z) < 0.56) return 0;
|
|
|
|
|
if (dir_x == 0 && dir_z == 0) return 0;
|
|
|
|
|
double len = sqrt((double)dir_x * dir_x + (double)dir_z * dir_z);
|
|
|
|
|
double ux = (double)dir_x / len;
|
|
|
|
|
double uz = (double)dir_z / len;
|
|
|
|
|
int bank_forward = 0, bank_backward = 0;
|
|
|
|
|
for (int distance = 4; distance <= 48; distance += 4) {
|
|
|
|
|
int fx = x + (int)llround(ux * distance);
|
|
|
|
|
int fz = z + (int)llround(uz * distance);
|
|
|
|
|
int bx = x - (int)llround(ux * distance);
|
|
|
|
|
int bz = z - (int)llround(uz * distance);
|
|
|
|
|
if (!bank_forward && is_dry_sea_level_bank(ctx, fx, fz)) bank_forward = 1;
|
|
|
|
|
if (!bank_backward && is_dry_sea_level_bank(ctx, bx, bz)) bank_backward = 1;
|
|
|
|
|
if (bank_forward && bank_backward) return 1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void append_trail_segment(worldgen_ctx *ctx, int ax, int az, int bx, int bz, int *points, int count) {
|
|
|
|
|
/* Drop near-parallel segments that hug existing roads to keep the network from bunching up. */
|
|
|
|
|
const double min_parallel_distance = 72.0;
|
|
|
|
|
@@ -597,6 +639,14 @@ double worldgen_debug_redwood_mask(worldgen_ctx *ctx, int x, int z) {
|
|
|
|
|
return redwood_biome_mask(ctx, x, z, height, local_relief(ctx, x, z, height));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double worldgen_debug_community_strength(worldgen_ctx *ctx, int x, int z) {
|
|
|
|
|
return woodland_community_strength(ctx, x, z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int worldgen_debug_bridge_site(worldgen_ctx *ctx, int x, int z, int dir_x, int dir_z) {
|
|
|
|
|
return small_community_water_crossing(ctx, x, z, dir_x, dir_z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void block_list_init(block_list *list) {
|
|
|
|
|
list->items = NULL;
|
|
|
|
|
list->count = 0;
|
|
|
|
|
@@ -1067,6 +1117,20 @@ static double old_growth_grove_mask(worldgen_ctx *ctx, int x, int z) {
|
|
|
|
|
return clamp01(cluster * 0.7 + noise * 0.3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static double forest_clearing_mask(worldgen_ctx *ctx, int x, int z) {
|
|
|
|
|
double cell = worley_distance(x - 23000, z + 17000, 0.0032,
|
|
|
|
|
0xC1EA4123u ^ (uint32_t)ctx->world_seed);
|
|
|
|
|
double opening = smoothstep01((0.28 - cell) / 0.16);
|
|
|
|
|
return opening;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static double forest_maturity_mask(worldgen_ctx *ctx, int x, int z, double cover) {
|
|
|
|
|
double age = simplex_noise2(&ctx->noise, (x + 44000) * 0.0011, (z - 36000) * 0.0011) * 0.5 + 0.5;
|
|
|
|
|
double disturbance = simplex_noise2(&ctx->noise, (x - 15000) * 0.0045, (z + 26000) * 0.0045) * 0.5 + 0.5;
|
|
|
|
|
double interior = smoothstep01((cover - 0.48) / 0.32);
|
|
|
|
|
return clamp01(age * 0.5 + interior * 0.38 + disturbance * 0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
@@ -1857,6 +1921,39 @@ static const int TREE_POOL_OLD_GROWTH[] = {
|
|
|
|
|
TREE_SPRUCE_TIERS, TREE_PINE_CROWN
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static double tree_species_stand(worldgen_ctx *ctx, int x, int z, double ox, double oz) {
|
|
|
|
|
double broad = simplex_noise2(&ctx->noise, (x + ox) * 0.0022, (z + oz) * 0.0022) * 0.5 + 0.5;
|
|
|
|
|
double detail = simplex_noise2(&ctx->noise, (x - oz) * 0.006, (z + ox) * 0.006) * 0.5 + 0.5;
|
|
|
|
|
return broad * 0.78 + detail * 0.22;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Nearby trees usually share a dominant species. A small companion-tree
|
|
|
|
|
* chance below preserves mixed forests without producing species confetti. */
|
|
|
|
|
static tree_species dominant_tree_species(worldgen_ctx *ctx, const column_data *data, int x, int z) {
|
|
|
|
|
double rain = rainfall_field(ctx, x, z);
|
|
|
|
|
int altitude = data->height - ctx->sea_level;
|
|
|
|
|
tree_species best = TREE_SPECIES_OAK;
|
|
|
|
|
double best_score = -1.0;
|
|
|
|
|
#define CONSIDER_SPECIES(species_id, score_value) do { \
|
|
|
|
|
double candidate_score = (score_value); \
|
|
|
|
|
if (candidate_score > best_score) { best_score = candidate_score; best = (species_id); } \
|
|
|
|
|
} while (0)
|
|
|
|
|
if (data->biome == BIOME_WEST_KY_COALFIELDS) {
|
|
|
|
|
CONSIDER_SPECIES(TREE_SPECIES_OAK, tree_species_stand(ctx, x, z, 11000, -7000) + 0.12);
|
|
|
|
|
CONSIDER_SPECIES(TREE_SPECIES_WALNUT, tree_species_stand(ctx, x, z, -19000, 13000) + rain * 0.08);
|
|
|
|
|
CONSIDER_SPECIES(TREE_SPECIES_MAPLE, tree_species_stand(ctx, x, z, 27000, 21000) + rain * 0.16);
|
|
|
|
|
CONSIDER_SPECIES(TREE_SPECIES_CYPRESS, tree_species_stand(ctx, x, z, -33000, -17000) + rain * 0.28 - 0.12);
|
|
|
|
|
} else {
|
|
|
|
|
CONSIDER_SPECIES(TREE_SPECIES_OAK, tree_species_stand(ctx, x, z, 11000, -7000) - altitude * 0.004);
|
|
|
|
|
CONSIDER_SPECIES(TREE_SPECIES_MAPLE, tree_species_stand(ctx, x, z, 27000, 21000) + rain * 0.12);
|
|
|
|
|
CONSIDER_SPECIES(TREE_SPECIES_BIRCH, tree_species_stand(ctx, x, z, -8000, 31000) + altitude * 0.006);
|
|
|
|
|
CONSIDER_SPECIES(TREE_SPECIES_PINE, tree_species_stand(ctx, x, z, 39000, -29000) + altitude * 0.008);
|
|
|
|
|
CONSIDER_SPECIES(TREE_SPECIES_SPRUCE, tree_species_stand(ctx, x, z, -41000, 37000) + altitude * 0.011 + rain * 0.08);
|
|
|
|
|
}
|
|
|
|
|
#undef CONSIDER_SPECIES
|
|
|
|
|
return best;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const tree_archetype *choose_tree_archetype(worldgen_ctx *ctx, const column_data *data, int world_x, int world_z, rng_state *rng) {
|
|
|
|
|
int altitude = data->height - ctx->sea_level;
|
|
|
|
|
const tree_archetype *fallback = &TREE_TYPES[TREE_OAK_ROUND];
|
|
|
|
|
@@ -1879,10 +1976,18 @@ static const tree_archetype *choose_tree_archetype(worldgen_ctx *ctx, const colu
|
|
|
|
|
return &TREE_TYPES[TREE_POOL_OLD_GROWTH[idx]];
|
|
|
|
|
}
|
|
|
|
|
if (data->biome == BIOME_WEST_KY_COALFIELDS) {
|
|
|
|
|
if (rng_next_f64(rng) < 0.82) {
|
|
|
|
|
const tree_archetype *stand_tree = choose_species_variant(dominant_tree_species(ctx, data, world_x, world_z), rng);
|
|
|
|
|
if (stand_tree) return stand_tree;
|
|
|
|
|
}
|
|
|
|
|
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]];
|
|
|
|
|
}
|
|
|
|
|
if (rng_next_f64(rng) < 0.82) {
|
|
|
|
|
const tree_archetype *stand_tree = choose_species_variant(dominant_tree_species(ctx, data, world_x, world_z), rng);
|
|
|
|
|
if (stand_tree) return stand_tree;
|
|
|
|
|
}
|
|
|
|
|
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]);
|
|
|
|
|
@@ -1891,9 +1996,15 @@ static const tree_archetype *choose_tree_archetype(worldgen_ctx *ctx, const colu
|
|
|
|
|
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) {
|
|
|
|
|
static void generate_tree(worldgen_ctx *ctx, const tree_archetype *arch, int x, int y, int z,
|
|
|
|
|
double maturity, rng_state *rng, block_list *out) {
|
|
|
|
|
if (!arch) return;
|
|
|
|
|
int height = rng_range_inclusive(rng, arch->min_height, arch->max_height);
|
|
|
|
|
/* Preserve the proportions expected by each canopy builder; maturity now
|
|
|
|
|
* adds subtle height variation without reducing young trees to bare poles. */
|
|
|
|
|
double height_scale = 0.86 + clamp01(maturity) * 0.14;
|
|
|
|
|
height = (int)llround((double)height * height_scale);
|
|
|
|
|
if (height < 4) height = 4;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
@@ -2114,6 +2225,8 @@ typedef struct {
|
|
|
|
|
int offset_z;
|
|
|
|
|
int half_w;
|
|
|
|
|
int half_l;
|
|
|
|
|
int roof_axis;
|
|
|
|
|
int stories;
|
|
|
|
|
} cabin_rect;
|
|
|
|
|
|
|
|
|
|
#define CABIN_MAX_RECTS 4
|
|
|
|
|
@@ -2139,18 +2252,36 @@ typedef struct {
|
|
|
|
|
int door_rect_index;
|
|
|
|
|
int has_ladder;
|
|
|
|
|
int path_width;
|
|
|
|
|
int porch_depth;
|
|
|
|
|
int has_chimney;
|
|
|
|
|
} cabin_blueprint;
|
|
|
|
|
|
|
|
|
|
static const cabin_rect CABIN_RECT_SMALL[] = {
|
|
|
|
|
{0, 0, 2, 3}
|
|
|
|
|
{0, 0, 2, 3, 0, 0}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const cabin_rect CABIN_RECT_LONG[] = {
|
|
|
|
|
{0, 0, 3, 4}
|
|
|
|
|
{0, 0, 3, 4, 0, 0}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const cabin_rect CABIN_RECT_TWO_STORY[] = {
|
|
|
|
|
{0, 0, 3, 3}
|
|
|
|
|
{0, 0, 3, 3, 1, 0}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const cabin_rect CABIN_RECT_L_SHAPED[] = {
|
|
|
|
|
{-1, 0, 2, 3, 0, 1}, {2, 1, 2, 1, 1, 1}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const cabin_rect CABIN_RECT_CROSS_GABLE[] = {
|
|
|
|
|
{0, 0, 2, 4, 0, 1}, {0, 0, 3, 2, 1, 1}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const cabin_rect CABIN_RECT_TOWER_LODGE[] = {
|
|
|
|
|
{-1, 0, 2, 3, 0, 1}, {2, 1, 1, 1, 1, 2}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const cabin_rect CABIN_RECT_RAMBLING[] = {
|
|
|
|
|
{0, 0, 2, 3, 0, 1}, {-2, -2, 1, 2, 1, 1}, {2, 2, 1, 2, 1, 1}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const cabin_blueprint CABIN_BLUEPRINTS[] = {
|
|
|
|
|
@@ -2174,7 +2305,9 @@ static const cabin_blueprint CABIN_BLUEPRINTS[] = {
|
|
|
|
|
0.08,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
2
|
|
|
|
|
2,
|
|
|
|
|
1,
|
|
|
|
|
1
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
CABIN_RECT_LONG,
|
|
|
|
|
@@ -2196,7 +2329,9 @@ static const cabin_blueprint CABIN_BLUEPRINTS[] = {
|
|
|
|
|
0.1,
|
|
|
|
|
0,
|
|
|
|
|
0,
|
|
|
|
|
3
|
|
|
|
|
3,
|
|
|
|
|
2,
|
|
|
|
|
1
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
CABIN_RECT_TWO_STORY,
|
|
|
|
|
@@ -2218,7 +2353,53 @@ static const cabin_blueprint CABIN_BLUEPRINTS[] = {
|
|
|
|
|
0.08,
|
|
|
|
|
0,
|
|
|
|
|
1,
|
|
|
|
|
2
|
|
|
|
|
2,
|
|
|
|
|
1,
|
|
|
|
|
1
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
.rects = CABIN_RECT_L_SHAPED,
|
|
|
|
|
.rect_count = sizeof(CABIN_RECT_L_SHAPED) / sizeof(CABIN_RECT_L_SHAPED[0]),
|
|
|
|
|
.wall_height = 4, .stories = 1,
|
|
|
|
|
.floor_block = BLOCK_OAK_PLANKS, .roof_block = BLOCK_SPRUCE_PLANKS,
|
|
|
|
|
.log_x_block = BLOCK_SPRUCE_LOG_X, .log_z_block = BLOCK_SPRUCE_LOG_Z, .corner_log = BLOCK_OAK_LOG,
|
|
|
|
|
.stair_e_block = BLOCK_SPRUCE_STAIRS_E, .stair_w_block = BLOCK_SPRUCE_STAIRS_W,
|
|
|
|
|
.window_block = BLOCK_GLASS_PANE, .foundation_block = BLOCK_STONE,
|
|
|
|
|
.log_decay = 0.025, .plank_decay = 0.12, .window_chance = 0.62, .door_decay = 0.04,
|
|
|
|
|
.door_rect_index = 0, .has_ladder = 0, .path_width = 3, .porch_depth = 2, .has_chimney = 1
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
.rects = CABIN_RECT_CROSS_GABLE,
|
|
|
|
|
.rect_count = sizeof(CABIN_RECT_CROSS_GABLE) / sizeof(CABIN_RECT_CROSS_GABLE[0]),
|
|
|
|
|
.wall_height = 4, .stories = 1,
|
|
|
|
|
.floor_block = BLOCK_SPRUCE_PLANKS, .roof_block = BLOCK_OAK_PLANKS,
|
|
|
|
|
.log_x_block = BLOCK_OAK_LOG_X, .log_z_block = BLOCK_OAK_LOG_Z, .corner_log = BLOCK_OAK_LOG,
|
|
|
|
|
.stair_e_block = BLOCK_SPRUCE_STAIRS_E, .stair_w_block = BLOCK_SPRUCE_STAIRS_W,
|
|
|
|
|
.window_block = BLOCK_GLASS, .foundation_block = BLOCK_STONE_BRICKS,
|
|
|
|
|
.log_decay = 0.015, .plank_decay = 0.08, .window_chance = 0.7, .door_decay = 0.02,
|
|
|
|
|
.door_rect_index = 1, .has_ladder = 0, .path_width = 3, .porch_depth = 2, .has_chimney = 1
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
.rects = CABIN_RECT_TOWER_LODGE,
|
|
|
|
|
.rect_count = sizeof(CABIN_RECT_TOWER_LODGE) / sizeof(CABIN_RECT_TOWER_LODGE[0]),
|
|
|
|
|
.wall_height = 4, .stories = 1,
|
|
|
|
|
.floor_block = BLOCK_SPRUCE_PLANKS, .roof_block = BLOCK_SPRUCE_PLANKS,
|
|
|
|
|
.log_x_block = BLOCK_OAK_LOG_X, .log_z_block = BLOCK_OAK_LOG_Z, .corner_log = BLOCK_OAK_LOG,
|
|
|
|
|
.stair_e_block = BLOCK_SPRUCE_STAIRS_E, .stair_w_block = BLOCK_SPRUCE_STAIRS_W,
|
|
|
|
|
.window_block = BLOCK_GLASS_PANE, .foundation_block = BLOCK_STONE,
|
|
|
|
|
.log_decay = 0.02, .plank_decay = 0.1, .window_chance = 0.72, .door_decay = 0.03,
|
|
|
|
|
.door_rect_index = 0, .has_ladder = 1, .path_width = 2, .porch_depth = 1, .has_chimney = 1
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
.rects = CABIN_RECT_RAMBLING,
|
|
|
|
|
.rect_count = sizeof(CABIN_RECT_RAMBLING) / sizeof(CABIN_RECT_RAMBLING[0]),
|
|
|
|
|
.wall_height = 3, .stories = 1,
|
|
|
|
|
.floor_block = BLOCK_OAK_PLANKS, .roof_block = BLOCK_SPRUCE_PLANKS,
|
|
|
|
|
.log_x_block = BLOCK_SPRUCE_LOG_X, .log_z_block = BLOCK_SPRUCE_LOG_Z, .corner_log = BLOCK_OAK_LOG,
|
|
|
|
|
.stair_e_block = BLOCK_SPRUCE_STAIRS_E, .stair_w_block = BLOCK_SPRUCE_STAIRS_W,
|
|
|
|
|
.window_block = BLOCK_GLASS_PANE, .foundation_block = BLOCK_STONE,
|
|
|
|
|
.log_decay = 0.035, .plank_decay = 0.16, .window_chance = 0.58, .door_decay = 0.06,
|
|
|
|
|
.door_rect_index = 0, .has_ladder = 0, .path_width = 2, .porch_depth = 2, .has_chimney = 1
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@@ -2297,7 +2478,7 @@ static int cabin_site_height_range(const cabin_blueprint *bp, int chunk_origin_x
|
|
|
|
|
|
|
|
|
|
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 roof_axis, 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;
|
|
|
|
|
@@ -2305,14 +2486,18 @@ static void build_cabin_roof(const cabin_blueprint *bp, int chunk_x, int chunk_z
|
|
|
|
|
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;
|
|
|
|
|
int layers = (roof_axis ? half_l : 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 start_cross = (roof_axis ? z0 : x0) - overhang + layer;
|
|
|
|
|
int end_cross = (roof_axis ? z1 : x1) + overhang - layer;
|
|
|
|
|
if (start_cross > end_cross) 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) {
|
|
|
|
|
int run0 = (roof_axis ? x0 : z0) - overhang;
|
|
|
|
|
int run1 = (roof_axis ? x1 : z1) + overhang;
|
|
|
|
|
for (int run = run0; run <= run1; ++run) {
|
|
|
|
|
for (int cross = start_cross; cross <= end_cross; ++cross) {
|
|
|
|
|
int wx = roof_axis ? run : cross;
|
|
|
|
|
int wz = roof_axis ? cross : run;
|
|
|
|
|
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;
|
|
|
|
|
@@ -2320,18 +2505,19 @@ static void build_cabin_roof(const cabin_blueprint *bp, int chunk_x, int chunk_z
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
for (int run = run0; run <= run1; ++run) {
|
|
|
|
|
int wx0 = roof_axis ? run : start_cross - 1;
|
|
|
|
|
int wz0 = roof_axis ? start_cross - 1 : run;
|
|
|
|
|
int wx1 = roof_axis ? run : end_cross + 1;
|
|
|
|
|
int wz1 = roof_axis ? end_cross + 1 : run;
|
|
|
|
|
int lx0 = wx0 - chunk_origin_x, lz0 = wz0 - chunk_origin_z;
|
|
|
|
|
int lx1 = wx1 - chunk_origin_x, lz1 = wz1 - chunk_origin_z;
|
|
|
|
|
uint16_t low_stair = roof_axis ? BLOCK_SPRUCE_STAIRS_S : bp->stair_e_block;
|
|
|
|
|
uint16_t high_stair = roof_axis ? BLOCK_SPRUCE_STAIRS_N : bp->stair_w_block;
|
|
|
|
|
if (lx0 >= 0 && lx0 < CHUNK_SIZE && lz0 >= 0 && lz0 < CHUNK_SIZE)
|
|
|
|
|
set_block_with_height(chunk, lx0, lz0, y, low_stair);
|
|
|
|
|
if (lx1 >= 0 && lx1 < CHUNK_SIZE && lz1 >= 0 && lz1 < CHUNK_SIZE)
|
|
|
|
|
set_block_with_height(chunk, lx1, lz1, y, high_stair);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -2375,18 +2561,7 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
int rect_stories = rect->stories > 0 ? rect->stories : bp->stories;
|
|
|
|
|
|
|
|
|
|
uint16_t foundation_block = bp->foundation_block ? bp->foundation_block : BLOCK_STONE;
|
|
|
|
|
for (int wz = z0; wz <= z1; ++wz) {
|
|
|
|
|
@@ -2414,7 +2589,7 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const
|
|
|
|
|
|
|
|
|
|
int ladder_hole_x = INT_MIN;
|
|
|
|
|
int ladder_hole_z = INT_MIN;
|
|
|
|
|
for (int story = 0; story < bp->stories; ++story) {
|
|
|
|
|
for (int story = 0; story < rect_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) {
|
|
|
|
|
@@ -2521,7 +2696,7 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (bp->has_ladder && story < bp->stories - 1) {
|
|
|
|
|
if (bp->has_ladder && story < rect_stories - 1) {
|
|
|
|
|
int ladder_x = rect_center_x;
|
|
|
|
|
int ladder_z = rect_center_z;
|
|
|
|
|
uint16_t ladder_block = BLOCK_LADDER_S;
|
|
|
|
|
@@ -2549,8 +2724,9 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 roof_base = base_floor_y + bp->wall_height * rect_stories;
|
|
|
|
|
build_cabin_roof(bp, chunk_x, chunk_z, chunk, rect_center_x, rect_center_z,
|
|
|
|
|
rect->half_w, rect->half_l, rect->roof_axis, roof_base, rng);
|
|
|
|
|
|
|
|
|
|
int attic_y = roof_base;
|
|
|
|
|
for (int wz = z0 + 1; wz <= z1 - 1; ++wz) {
|
|
|
|
|
@@ -2578,6 +2754,41 @@ static void build_cabin_rect(worldgen_ctx *ctx, const cabin_blueprint *bp, const
|
|
|
|
|
update_columns_for_rect(columns, chunk_origin_x, chunk_origin_z, x0, x1, z0, z1, roof_base + 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void build_cabin_porch_and_chimney(const cabin_blueprint *bp, const cabin_rect *door_rect,
|
|
|
|
|
int chunk_x, int chunk_z, chunk_data *chunk, int rect_center_x, int rect_center_z,
|
|
|
|
|
int base_floor_y, int door_x, int door_z, int door_side) {
|
|
|
|
|
int origin_x = chunk_x * CHUNK_SIZE;
|
|
|
|
|
int origin_z = chunk_z * CHUNK_SIZE;
|
|
|
|
|
int out_x = door_side == 2 ? -1 : door_side == 3 ? 1 : 0;
|
|
|
|
|
int out_z = door_side == 0 ? -1 : door_side == 1 ? 1 : 0;
|
|
|
|
|
int side_x = out_z;
|
|
|
|
|
int side_z = out_x;
|
|
|
|
|
for (int depth = 1; depth <= bp->porch_depth; ++depth) {
|
|
|
|
|
for (int side = -1; side <= 1; ++side) {
|
|
|
|
|
int wx = door_x + out_x * depth + side_x * side;
|
|
|
|
|
int wz = door_z + out_z * depth + side_z * side;
|
|
|
|
|
int lx = wx - origin_x, lz = wz - origin_z;
|
|
|
|
|
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
|
|
|
|
|
set_block_with_height(chunk, lx, lz, base_floor_y, bp->floor_block);
|
|
|
|
|
set_block_with_height(chunk, lx, lz, base_floor_y + 4, bp->roof_block);
|
|
|
|
|
if (depth == bp->porch_depth && (side == -1 || side == 1)) {
|
|
|
|
|
for (int y = base_floor_y + 1; y <= base_floor_y + 3; ++y)
|
|
|
|
|
set_block_with_height(chunk, lx, lz, y, bp->corner_log);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!bp->has_chimney) return;
|
|
|
|
|
int chimney_x = rect_center_x + (door_side == 2 ? door_rect->half_w - 1 : -door_rect->half_w + 1);
|
|
|
|
|
int chimney_z = rect_center_z + (door_side == 0 ? door_rect->half_l - 1 : -door_rect->half_l + 1);
|
|
|
|
|
int lx = chimney_x - origin_x, lz = chimney_z - origin_z;
|
|
|
|
|
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) return;
|
|
|
|
|
int rect_stories = door_rect->stories > 0 ? door_rect->stories : bp->stories;
|
|
|
|
|
int roof_top = base_floor_y + bp->wall_height * rect_stories +
|
|
|
|
|
(door_rect->roof_axis ? door_rect->half_l : door_rect->half_w) + 4;
|
|
|
|
|
for (int y = base_floor_y + 1; y <= roof_top && y < CHUNK_HEIGHT; ++y)
|
|
|
|
|
set_block_with_height(chunk, lx, lz, y, BLOCK_STONE_BRICKS);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
@@ -2592,6 +2803,7 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da
|
|
|
|
|
int rect_z1[CABIN_MAX_RECTS];
|
|
|
|
|
int min_h = INT_MAX;
|
|
|
|
|
int max_h = INT_MIN;
|
|
|
|
|
int max_structure_height = 0;
|
|
|
|
|
for (size_t i = 0; i < bp->rect_count; ++i) {
|
|
|
|
|
const cabin_rect *rect = &bp->rects[i];
|
|
|
|
|
int rcx = center_x + rect->offset_x;
|
|
|
|
|
@@ -2608,6 +2820,10 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da
|
|
|
|
|
rect_x1[i] = x1;
|
|
|
|
|
rect_z0[i] = z0;
|
|
|
|
|
rect_z1[i] = z1;
|
|
|
|
|
int rect_stories = rect->stories > 0 ? rect->stories : bp->stories;
|
|
|
|
|
int roof_rise = (rect->roof_axis ? rect->half_l : rect->half_w) + 4;
|
|
|
|
|
int structure_height = bp->wall_height * rect_stories + roof_rise;
|
|
|
|
|
if (structure_height > max_structure_height) max_structure_height = structure_height;
|
|
|
|
|
if (rect_overlaps_mask(occupancy, chunk_origin_x, chunk_origin_z, x0, x1, z0, z1, 3)) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
@@ -2636,7 +2852,7 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da
|
|
|
|
|
if (!cabin_site_height_range(bp, chunk_origin_x, chunk_origin_z, columns, center_x, center_z, 2, &pad_min_h, &pad_max_h)) return 0;
|
|
|
|
|
if (pad_max_h - pad_min_h > 2) return 0;
|
|
|
|
|
int base_floor_y = min_h + 1;
|
|
|
|
|
if (base_floor_y + bp->wall_height * bp->stories + 6 >= CHUNK_HEIGHT) return 0;
|
|
|
|
|
if (base_floor_y + max_structure_height >= 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);
|
|
|
|
|
@@ -2670,6 +2886,19 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da
|
|
|
|
|
door_z = clamp_int(door_z + door_offset, min_door_z, max_door_z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Clear the complete compound footprint before building any wing. Clearing
|
|
|
|
|
* per rectangle would erase walls and roofs where wings overlap. */
|
|
|
|
|
for (size_t i = 0; i < bp->rect_count; ++i) {
|
|
|
|
|
for (int wz = rect_z0[i] - 2; wz <= rect_z1[i] + 2; ++wz) {
|
|
|
|
|
for (int wx = rect_x0[i] - 2; wx <= rect_x1[i] + 2; ++wx) {
|
|
|
|
|
int lx = wx - chunk_origin_x, lz = wz - chunk_origin_z;
|
|
|
|
|
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) continue;
|
|
|
|
|
for (int y = base_floor_y; y <= base_floor_y + max_structure_height && y < CHUNK_HEIGHT; ++y)
|
|
|
|
|
set_block_with_height(chunk, lx, lz, y, BLOCK_AIR);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
@@ -2677,6 +2906,9 @@ static int try_place_cabin(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_da
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
build_cabin_porch_and_chimney(bp, &bp->rects[door_rect_index], chunk_x, chunk_z, chunk,
|
|
|
|
|
rect_center_x[door_rect_index], rect_center_z[door_rect_index],
|
|
|
|
|
base_floor_y, door_x, door_z, door_side);
|
|
|
|
|
|
|
|
|
|
connect_cabin_to_trail(ctx, chunk_x, chunk_z, columns, chunk, door_x, door_z, door_side, bp->path_width);
|
|
|
|
|
|
|
|
|
|
@@ -2688,7 +2920,10 @@ static void generate_chunk_cabins(worldgen_ctx *ctx, int chunk_x, int chunk_z, c
|
|
|
|
|
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;
|
|
|
|
|
int chunk_center_x = chunk_x * CHUNK_SIZE + CHUNK_SIZE / 2;
|
|
|
|
|
int chunk_center_z = chunk_z * CHUNK_SIZE + CHUNK_SIZE / 2;
|
|
|
|
|
double community = woodland_community_strength(ctx, chunk_center_x, chunk_center_z);
|
|
|
|
|
double spawn_chance = 0.008 + noise * 0.015 + smoothstep01((community - 0.48) / 0.34) * 0.085;
|
|
|
|
|
if (ctx->enable_trails && !chunk_contains_trail(ctx, chunk_x, chunk_z)) {
|
|
|
|
|
return; /* only spawn cabins where a road crosses the chunk, so the spur can hook in */
|
|
|
|
|
}
|
|
|
|
|
@@ -2725,6 +2960,7 @@ static void generate_chunk_cabins(worldgen_ctx *ctx, int chunk_x, int chunk_z, c
|
|
|
|
|
int world_cx = chunk_x * CHUNK_SIZE + local_cx;
|
|
|
|
|
int world_cz = chunk_z * CHUNK_SIZE + local_cz;
|
|
|
|
|
double score = land_value(ctx, world_cx, world_cz);
|
|
|
|
|
score += woodland_community_strength(ctx, world_cx, world_cz) * 0.35;
|
|
|
|
|
int flat_min_h = INT_MAX;
|
|
|
|
|
int flat_max_h = INT_MIN;
|
|
|
|
|
if (!cabin_site_height_range(bp, chunk_x * CHUNK_SIZE, chunk_z * CHUNK_SIZE, columns,
|
|
|
|
|
@@ -2738,10 +2974,10 @@ static void generate_chunk_cabins(worldgen_ctx *ctx, int chunk_x, int chunk_z, c
|
|
|
|
|
double dist2 = nearest_trail_distance2(ctx, world_cx, world_cz, 1.0e12);
|
|
|
|
|
double bonus = 0.0;
|
|
|
|
|
double dist = sqrt(dist2);
|
|
|
|
|
if (dist < 640.0) {
|
|
|
|
|
double t = clamp01(1.0 - dist / 640.0);
|
|
|
|
|
bonus += 0.45 * t;
|
|
|
|
|
}
|
|
|
|
|
/* Favor a wooded setback instead of putting the building footprint
|
|
|
|
|
* directly on the centerline. The spur still handles the connection. */
|
|
|
|
|
if (dist < 8.0) bonus -= (8.0 - dist) * 0.9;
|
|
|
|
|
else if (dist < 96.0) bonus += 0.45 * clamp01(1.0 - fabs(dist - 20.0) / 76.0);
|
|
|
|
|
score += bonus;
|
|
|
|
|
}
|
|
|
|
|
if (score > best_score) {
|
|
|
|
|
@@ -2956,6 +3192,35 @@ static int column_has_manmade_surface(chunk_data *chunk, int chunk_x, int chunk_
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void tree_grid_candidate(worldgen_ctx *ctx, int gx, int gz, int grid, int *out_x, int *out_z) {
|
|
|
|
|
uint64_t seed = (uint64_t)ctx->world_seed + (uint64_t)gx * 341873128712ULL + (uint64_t)gz * 132897987541ULL;
|
|
|
|
|
rng_state rng;
|
|
|
|
|
rng_seed(&rng, seed);
|
|
|
|
|
*out_x = gx * grid + rng_range_inclusive(&rng, 0, grid - 1);
|
|
|
|
|
*out_z = gz * grid + rng_range_inclusive(&rng, 0, grid - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Matérn-style priority thinning gives deterministic, irregular spacing and
|
|
|
|
|
* prevents adjacent grid cells from producing colliding crowns. */
|
|
|
|
|
static int tree_wins_spacing(worldgen_ctx *ctx, int gx, int gz, int grid, int x, int z, double spacing) {
|
|
|
|
|
int reach = (int)ceil(spacing / (double)grid) + 1;
|
|
|
|
|
uint32_t priority = hash_coords(gx, gz, (uint32_t)ctx->world_seed ^ 0x71EE5A9Du);
|
|
|
|
|
double spacing2 = spacing * spacing;
|
|
|
|
|
for (int ox = -reach; ox <= reach; ++ox) {
|
|
|
|
|
for (int oz = -reach; oz <= reach; ++oz) {
|
|
|
|
|
if (ox == 0 && oz == 0) continue;
|
|
|
|
|
int ngx = gx + ox, ngz = gz + oz;
|
|
|
|
|
int nx, nz;
|
|
|
|
|
tree_grid_candidate(ctx, ngx, ngz, grid, &nx, &nz);
|
|
|
|
|
double dx = (double)(nx - x), dz = (double)(nz - z);
|
|
|
|
|
if (dx * dx + dz * dz >= spacing2) continue;
|
|
|
|
|
uint32_t other = hash_coords(ngx, ngz, (uint32_t)ctx->world_seed ^ 0x71EE5A9Du);
|
|
|
|
|
if (other < priority || (other == priority && (ngx < gx || (ngx == gx && ngz < gz)))) return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
@@ -2981,8 +3246,8 @@ static void generate_chunk_trees(worldgen_ctx *ctx, int chunk_x, int chunk_z, ch
|
|
|
|
|
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);
|
|
|
|
|
int candidate_x, candidate_z;
|
|
|
|
|
tree_grid_candidate(ctx, gx, gz, grid, &candidate_x, &candidate_z);
|
|
|
|
|
|
|
|
|
|
if (candidate_x < chunk_min_x || candidate_x > chunk_max_x || candidate_z < chunk_min_z || candidate_z > chunk_max_z) {
|
|
|
|
|
continue;
|
|
|
|
|
@@ -3024,21 +3289,32 @@ static void generate_chunk_trees(worldgen_ctx *ctx, int chunk_x, int chunk_z, ch
|
|
|
|
|
}
|
|
|
|
|
if (altitude_factor <= 0.0) continue;
|
|
|
|
|
|
|
|
|
|
double base_cover = tree_density_mask(ctx, candidate_x, candidate_z);
|
|
|
|
|
double clearing = forest_clearing_mask(ctx, candidate_x, candidate_z);
|
|
|
|
|
double cover = clamp01(base_cover * (1.0 - clearing * 0.92));
|
|
|
|
|
double maturity = forest_maturity_mask(ctx, candidate_x, candidate_z, cover);
|
|
|
|
|
double spawn_prob;
|
|
|
|
|
if (is_redwood) {
|
|
|
|
|
double rain = rainfall_field(ctx, candidate_x, candidate_z);
|
|
|
|
|
spawn_prob = clamp01(0.25 + stand_density * 0.65 + (rain - 0.5) * 0.2) * altitude_factor;
|
|
|
|
|
maturity = clamp01(0.55 + stand_density * 0.45);
|
|
|
|
|
} else {
|
|
|
|
|
double density = tree_density_mask(ctx, candidate_x, candidate_z);
|
|
|
|
|
double density = cover;
|
|
|
|
|
if (is_old_growth) {
|
|
|
|
|
density = clamp01(0.25 + grove_mask * 0.75);
|
|
|
|
|
maturity = clamp01(0.72 + grove_mask * 0.28);
|
|
|
|
|
}
|
|
|
|
|
spawn_prob = 0.7 * (0.55 + 0.45 * density) * altitude_factor;
|
|
|
|
|
/* Openings retain a few isolated field trees, while forest interiors
|
|
|
|
|
* become substantially denser than their regenerating edges. */
|
|
|
|
|
spawn_prob = (0.08 + density * 0.9) * 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;
|
|
|
|
|
double spacing = is_redwood ? (8.0 + (1.0 - stand_density) * 3.0)
|
|
|
|
|
: (3.5 + (1.0 - cover) * 2.5 + maturity * 0.75);
|
|
|
|
|
if (!tree_wins_spacing(ctx, gx, gz, grid, candidate_x, candidate_z, spacing)) continue;
|
|
|
|
|
|
|
|
|
|
block_list tmp;
|
|
|
|
|
block_list_init(&tmp);
|
|
|
|
|
@@ -3052,7 +3328,7 @@ static void generate_chunk_trees(worldgen_ctx *ctx, int chunk_x, int chunk_z, ch
|
|
|
|
|
block_list_free(&tmp);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
generate_tree(ctx, arch, candidate_x, base_y, candidate_z, &rng, &tmp);
|
|
|
|
|
generate_tree(ctx, arch, candidate_x, base_y, candidate_z, maturity, &rng, &tmp);
|
|
|
|
|
if (tmp.count == 0) {
|
|
|
|
|
block_list_free(&tmp);
|
|
|
|
|
continue;
|
|
|
|
|
@@ -3109,6 +3385,34 @@ static uint32_t trail_segment_hash(int ax, int az, int bx, int bz, uint32_t seed
|
|
|
|
|
return hash_coords(mix_x, mix_z, seed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
double valley_depth;
|
|
|
|
|
double ridge_exposure;
|
|
|
|
|
double gradient_x;
|
|
|
|
|
double gradient_z;
|
|
|
|
|
} trail_landform;
|
|
|
|
|
|
|
|
|
|
/* Read the broad landform rather than reacting to individual blocks. This
|
|
|
|
|
* distinguishes sheltered valley floors from ridges and steep side slopes. */
|
|
|
|
|
static trail_landform sample_trail_landform(worldgen_ctx *ctx, int x, int z, double height) {
|
|
|
|
|
const int r = TRAIL_LANDFORM_RADIUS;
|
|
|
|
|
double east = (double)column_height(ctx, x + r, z);
|
|
|
|
|
double west = (double)column_height(ctx, x - r, z);
|
|
|
|
|
double south = (double)column_height(ctx, x, z + r);
|
|
|
|
|
double north = (double)column_height(ctx, x, z - r);
|
|
|
|
|
double se = (double)column_height(ctx, x + r, z + r);
|
|
|
|
|
double nw = (double)column_height(ctx, x - r, z - r);
|
|
|
|
|
double ne = (double)column_height(ctx, x + r, z - r);
|
|
|
|
|
double sw = (double)column_height(ctx, x - r, z + r);
|
|
|
|
|
double mean = (east + west + south + north + se + nw + ne + sw) / 8.0;
|
|
|
|
|
trail_landform land;
|
|
|
|
|
land.valley_depth = mean - height;
|
|
|
|
|
land.ridge_exposure = fmax(0.0, height - mean);
|
|
|
|
|
land.gradient_x = (east - west) / (2.0 * r);
|
|
|
|
|
land.gradient_z = (south - north) / (2.0 * r);
|
|
|
|
|
return land;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
@@ -3120,9 +3424,10 @@ static int should_connect_trail_nodes(worldgen_ctx *ctx, int node_x0, int node_z
|
|
|
|
|
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);
|
|
|
|
|
trail_landform midpoint_land = sample_trail_landform(ctx, mid_ix, mid_iz, (double)mid_column.height);
|
|
|
|
|
double valley_bias = clamp01((midpoint_land.valley_depth + 4.0) / 16.0);
|
|
|
|
|
if (mid_column.has_water && mid_column.height <= mid_column.water_surface) {
|
|
|
|
|
ridge_bias -= 0.35;
|
|
|
|
|
valley_bias -= 0.35;
|
|
|
|
|
}
|
|
|
|
|
double dir_x = bx - ax;
|
|
|
|
|
double dir_z = bz - az;
|
|
|
|
|
@@ -3167,7 +3472,7 @@ static int should_connect_trail_nodes(worldgen_ctx *ctx, int node_x0, int node_z
|
|
|
|
|
base += 0.07;
|
|
|
|
|
}
|
|
|
|
|
base += (alignment - 0.5) * 0.2;
|
|
|
|
|
base += (ridge_bias - 0.5) * 0.15;
|
|
|
|
|
base += (valley_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;
|
|
|
|
|
@@ -3279,11 +3584,15 @@ static double nearest_trail_distance2(worldgen_ctx *ctx, int x, int z, double fa
|
|
|
|
|
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 - x);
|
|
|
|
|
double dz = (double)(tz - z);
|
|
|
|
|
for (int p = 0; p < seg->count - 1; ++p) {
|
|
|
|
|
double d2 = point_segment_distance2((double)x, (double)z,
|
|
|
|
|
(double)seg->points[p * 2], (double)seg->points[p * 2 + 1],
|
|
|
|
|
(double)seg->points[(p + 1) * 2], (double)seg->points[(p + 1) * 2 + 1]);
|
|
|
|
|
if (d2 < best) best = d2;
|
|
|
|
|
}
|
|
|
|
|
if (seg->count == 1) {
|
|
|
|
|
double dx = (double)(seg->points[0] - x);
|
|
|
|
|
double dz = (double)(seg->points[1] - z);
|
|
|
|
|
double d2 = dx * dx + dz * dz;
|
|
|
|
|
if (d2 < best) best = d2;
|
|
|
|
|
}
|
|
|
|
|
@@ -3321,6 +3630,12 @@ static int build_trail_path(worldgen_ctx *ctx, double ax, double az, double bx,
|
|
|
|
|
if (grid_w < 2) grid_w = 2;
|
|
|
|
|
if (grid_h < 2) grid_h = 2;
|
|
|
|
|
int total = grid_w * grid_h;
|
|
|
|
|
double route_dx = bx - ax;
|
|
|
|
|
double route_dz = bz - az;
|
|
|
|
|
double route_length = sqrt(route_dx * route_dx + route_dz * route_dz);
|
|
|
|
|
/* Long inter-settlement roads are engineered more conservatively than
|
|
|
|
|
* short cabin spurs, which may accept steeper and more direct approaches. */
|
|
|
|
|
double road_scale = clamp01((route_length - 240.0) / 900.0);
|
|
|
|
|
double *heights = (double *)malloc((size_t)total * sizeof(double));
|
|
|
|
|
if (!heights) return 0;
|
|
|
|
|
for (int gz = 0; gz < grid_h; ++gz) {
|
|
|
|
|
@@ -3381,14 +3696,23 @@ static int build_trail_path(worldgen_ctx *ctx, double ax, double az, double bx,
|
|
|
|
|
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 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);
|
|
|
|
|
double water_penalty = 0.0;
|
|
|
|
|
if (cd.has_water && next_height <= cd.water_surface) {
|
|
|
|
|
continue; /* trails should never be routed through water */
|
|
|
|
|
if (!small_community_water_crossing(ctx, wx, wz, neighbors[n][0], neighbors[n][1])) continue;
|
|
|
|
|
next_height = (double)cd.water_surface + 1.0;
|
|
|
|
|
water_penalty = 8.0; /* worthwhile only when it materially shortens a community road */
|
|
|
|
|
}
|
|
|
|
|
int current_wx = (int)llround(min_x + cx * TRAIL_CELL_SIZE);
|
|
|
|
|
int current_wz = (int)llround(min_z + cz * TRAIL_CELL_SIZE);
|
|
|
|
|
column_data current_cd = get_column_data(ctx, current_wx, current_wz);
|
|
|
|
|
if (current_cd.has_water && current_cd.height < current_cd.water_surface)
|
|
|
|
|
current_height = (double)current_cd.water_surface + 1.0;
|
|
|
|
|
double slope = fabs(next_height - current_height);
|
|
|
|
|
double max_step = 4.0 - road_scale;
|
|
|
|
|
if (slope > max_step) continue;
|
|
|
|
|
double tree_penalty = 0.0;
|
|
|
|
|
if (cd.biome == BIOME_REDWOOD_FOREST) {
|
|
|
|
|
double stand = redwood_tree_presence(ctx, wx, wz);
|
|
|
|
|
@@ -3396,7 +3720,20 @@ static int build_trail_path(worldgen_ctx *ctx, double ax, double az, double bx,
|
|
|
|
|
tree_penalty = stand;
|
|
|
|
|
}
|
|
|
|
|
double base_cost = (neighbors[n][0] == 0 || neighbors[n][1] == 0) ? 1.0 : 1.41421356237;
|
|
|
|
|
double cost = base_cost * (1.0 + slope * 6.0 + tree_penalty * 4.5) + tree_penalty * 6.5;
|
|
|
|
|
trail_landform land = sample_trail_landform(ctx, wx, wz, next_height);
|
|
|
|
|
double valley_bonus = clamp01(land.valley_depth / 18.0) * (0.4 + road_scale * 0.2);
|
|
|
|
|
double ridge_penalty = clamp01(land.ridge_exposure / 22.0) * (1.4 + road_scale * 1.8);
|
|
|
|
|
double move_x = (double)neighbors[n][0] / base_cost;
|
|
|
|
|
double move_z = (double)neighbors[n][1] / base_cost;
|
|
|
|
|
double contour_climb = fabs(move_x * land.gradient_x + move_z * land.gradient_z);
|
|
|
|
|
double cross_relief = fabs(-move_z * land.gradient_x + move_x * land.gradient_z) *
|
|
|
|
|
(2.0 * TRAIL_LANDFORM_RADIUS);
|
|
|
|
|
double contour_penalty = contour_climb * (5.0 + road_scale * 5.0);
|
|
|
|
|
double earthwork_penalty = clamp01((cross_relief - 10.0) / 28.0) * (0.8 + road_scale * 1.2);
|
|
|
|
|
double grade_penalty = slope * (5.5 + road_scale * 3.5);
|
|
|
|
|
double terrain_cost = 1.0 + grade_penalty + ridge_penalty + contour_penalty +
|
|
|
|
|
earthwork_penalty + tree_penalty * 4.5 + water_penalty;
|
|
|
|
|
double cost = base_cost * terrain_cost * (1.0 - valley_bonus) + tree_penalty * 6.5;
|
|
|
|
|
if (dist[current] + cost < dist[ni]) {
|
|
|
|
|
dist[ni] = dist[current] + cost;
|
|
|
|
|
prev[ni] = current;
|
|
|
|
|
@@ -3556,6 +3893,26 @@ static void place_trail_column(chunk_data *out, column_data columns[CHUNK_SIZE][
|
|
|
|
|
columns[local_x][local_z].height = target_height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void place_trail_bridge_column(chunk_data *out, column_data columns[CHUNK_SIZE][CHUNK_SIZE],
|
|
|
|
|
int chunk_x, int chunk_z, int world_x, int world_z, int deck_y, int railing) {
|
|
|
|
|
int lx = world_x - chunk_x * CHUNK_SIZE;
|
|
|
|
|
int lz = world_z - chunk_z * CHUNK_SIZE;
|
|
|
|
|
if (lx < 0 || lx >= CHUNK_SIZE || lz < 0 || lz >= CHUNK_SIZE) return;
|
|
|
|
|
if (deck_y < 2 || deck_y >= CHUNK_HEIGHT - 4) return;
|
|
|
|
|
for (int y = 1; y < CHUNK_HEIGHT; ++y)
|
|
|
|
|
if (is_cabin_structure_block(out->blocks[y][lx][lz])) return;
|
|
|
|
|
out->blocks[deck_y][lx][lz] = BLOCK_SPRUCE_PLANKS;
|
|
|
|
|
for (int y = deck_y + 1; y <= deck_y + 3; ++y) out->blocks[y][lx][lz] = BLOCK_AIR;
|
|
|
|
|
uint32_t support_hash = hash_coords(world_x, world_z, 0xB21D6E5u);
|
|
|
|
|
if ((support_hash % 5u) == 0u) {
|
|
|
|
|
int bed = columns[lx][lz].height;
|
|
|
|
|
for (int y = bed + 1; y < deck_y; ++y) out->blocks[y][lx][lz] = BLOCK_OAK_LOG;
|
|
|
|
|
}
|
|
|
|
|
if (railing && (support_hash % 3u) == 0u) out->blocks[deck_y + 1][lx][lz] = BLOCK_OAK_LOG;
|
|
|
|
|
columns[lx][lz].height = deck_y;
|
|
|
|
|
out->heightmap[lx][lz] = (uint16_t)(deck_y + (railing ? 1 : 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
@@ -3565,6 +3922,8 @@ static void carve_trail_pad(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_d
|
|
|
|
|
if (dx * dx + dz * dz > radius_sq) continue;
|
|
|
|
|
int wx = center_x + dx;
|
|
|
|
|
int wz = center_z + dz;
|
|
|
|
|
column_data data = get_column_data(ctx, wx, wz);
|
|
|
|
|
if (data.has_water && data.height < data.water_surface) continue;
|
|
|
|
|
place_trail_column(out, columns, chunk_x, chunk_z, wx, wz, target_height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -3596,7 +3955,8 @@ static void carve_trail_span(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_
|
|
|
|
|
int wx = (int)llround(px);
|
|
|
|
|
int wz = (int)llround(pz);
|
|
|
|
|
column_data data = get_column_data(ctx, wx, wz);
|
|
|
|
|
int target = data.height;
|
|
|
|
|
int is_bridge = small_community_water_crossing(ctx, wx, wz, x1 - x0, z1 - z0);
|
|
|
|
|
int target = is_bridge ? data.water_surface + 1 : data.height;
|
|
|
|
|
if (last_height != INT32_MIN) {
|
|
|
|
|
double blended = carry_weight * (double)last_height + (1.0 - carry_weight) * (double)target;
|
|
|
|
|
target = (int)llround(blended);
|
|
|
|
|
@@ -3614,6 +3974,15 @@ static void carve_trail_span(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_
|
|
|
|
|
double oz = pz + nz * w;
|
|
|
|
|
int fx = (int)llround(ox);
|
|
|
|
|
int fz = (int)llround(oz);
|
|
|
|
|
column_data cross_data = get_column_data(ctx, fx, fz);
|
|
|
|
|
if (cross_data.has_water && cross_data.height < cross_data.water_surface) {
|
|
|
|
|
if (small_community_water_crossing(ctx, fx, fz, x1 - x0, z1 - z0)) {
|
|
|
|
|
int edge = fabs(w) >= half_width - 0.55;
|
|
|
|
|
place_trail_bridge_column(out, columns, chunk_x, chunk_z, fx, fz,
|
|
|
|
|
cross_data.water_surface + 1, edge);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
place_trail_column(out, columns, chunk_x, chunk_z, fx, fz, target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|