Add world generator sources and binary

This commit is contained in:
chelsea
2025-12-02 18:38:45 -06:00
parent 3f92f5add7
commit 2482740b89
10 changed files with 4163 additions and 0 deletions

818
worldgen-c/src/main.c Normal file
View File

@@ -0,0 +1,818 @@
#include "worldgen.h"
#include <errno.h>
#include <math.h>
#include <limits.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <zlib.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
typedef struct {
int x;
int z;
} chunk_coord;
typedef struct {
int chunk_x;
int chunk_z;
chunk_data data;
} completed_chunk;
typedef struct {
completed_chunk *chunks;
size_t capacity;
_Atomic size_t count;
pthread_mutex_t mutex;
} results_buffer;
typedef struct {
chunk_coord *items;
size_t count;
_Atomic size_t next_index;
_Atomic size_t done;
} job_queue;
typedef struct {
job_queue *queue;
const char *out_dir;
int world_seed;
int sea_level;
int snow_line;
pthread_mutex_t *log_mu;
int enable_trails;
results_buffer *results;
} worker_args;
typedef enum {
FORMAT_MCA,
FORMAT_BIN
} output_format;
// ---------------------------------------------------------------------------
// Block state palette (fixed mapping for our limited block set)
// ---------------------------------------------------------------------------
typedef struct {
const char *key;
const char *value;
} kv_pair;
typedef struct {
const char *name;
const kv_pair *props;
size_t prop_count;
} block_state;
static const kv_pair PROPS_LOG_AXIS[] = {{"axis", "y"}};
static const kv_pair PROPS_GRASS[] = {{"snowy", "false"}};
static const kv_pair PROPS_WATER[] = {{"level", "0"}};
static const kv_pair PROPS_LEAVES[] = {{"distance", "1"}, {"persistent", "false"}};
static const block_state BLOCK_STATE_TABLE[] = {
[BLOCK_BEDROCK] = {"minecraft:bedrock", NULL, 0},
[BLOCK_STONE] = {"minecraft:stone", NULL, 0},
[BLOCK_DIRT] = {"minecraft:dirt", NULL, 0},
[BLOCK_GRASS] = {"minecraft:grass_block", PROPS_GRASS, 1},
[BLOCK_WATER] = {"minecraft:water", PROPS_WATER, 1},
[BLOCK_AIR] = {"minecraft:air", NULL, 0},
[BLOCK_OAK_LOG] = {"minecraft:oak_log", PROPS_LOG_AXIS, 1},
[BLOCK_OAK_LEAVES] = {"minecraft:oak_leaves", PROPS_LEAVES, 2},
[BLOCK_BIRCH_LOG] = {"minecraft:birch_log", PROPS_LOG_AXIS, 1},
[BLOCK_BIRCH_LEAVES] = {"minecraft:birch_leaves", PROPS_LEAVES, 2},
[BLOCK_COAL] = {"minecraft:coal_ore", NULL, 0},
[BLOCK_SAND] = {"minecraft:sand", NULL, 0},
[BLOCK_GRAVEL] = {"minecraft:gravel", NULL, 0},
[BLOCK_SNOW] = {"minecraft:snow", NULL, 0},
[BLOCK_TALL_GRASS] = {"minecraft:grass", NULL, 0}
};
static const block_state *get_block_state(uint16_t id) {
if (id < sizeof(BLOCK_STATE_TABLE) / sizeof(BLOCK_STATE_TABLE[0]) && BLOCK_STATE_TABLE[id].name) {
return &BLOCK_STATE_TABLE[id];
}
return &BLOCK_STATE_TABLE[BLOCK_AIR];
}
// ---------------------------------------------------------------------------
// Dynamic byte buffer helpers
// ---------------------------------------------------------------------------
typedef struct {
uint8_t *data;
size_t len;
size_t cap;
} buf;
static void buf_reserve(buf *b, size_t extra) {
size_t need = b->len + extra;
if (need <= b->cap) return;
size_t new_cap = b->cap ? b->cap * 2 : 1024;
while (new_cap < need) new_cap *= 2;
uint8_t *new_data = (uint8_t *)realloc(b->data, new_cap);
if (!new_data) return;
b->data = new_data;
b->cap = new_cap;
}
static void buf_put_u8(buf *b, uint8_t v) {
buf_reserve(b, 1);
b->data[b->len++] = v;
}
static void buf_put_be16(buf *b, uint16_t v) {
buf_reserve(b, 2);
b->data[b->len++] = (uint8_t)(v >> 8);
b->data[b->len++] = (uint8_t)(v & 0xFF);
}
static void buf_put_be32(buf *b, uint32_t v) {
buf_reserve(b, 4);
b->data[b->len++] = (uint8_t)(v >> 24);
b->data[b->len++] = (uint8_t)((v >> 16) & 0xFF);
b->data[b->len++] = (uint8_t)((v >> 8) & 0xFF);
b->data[b->len++] = (uint8_t)(v & 0xFF);
}
static void buf_put_be64(buf *b, uint64_t v) {
buf_reserve(b, 8);
b->data[b->len++] = (uint8_t)(v >> 56);
b->data[b->len++] = (uint8_t)((v >> 48) & 0xFF);
b->data[b->len++] = (uint8_t)((v >> 40) & 0xFF);
b->data[b->len++] = (uint8_t)((v >> 32) & 0xFF);
b->data[b->len++] = (uint8_t)((v >> 24) & 0xFF);
b->data[b->len++] = (uint8_t)((v >> 16) & 0xFF);
b->data[b->len++] = (uint8_t)((v >> 8) & 0xFF);
b->data[b->len++] = (uint8_t)(v & 0xFF);
}
static void buf_append(buf *b, const uint8_t *data, size_t n) {
buf_reserve(b, n);
memcpy(b->data + b->len, data, n);
b->len += n;
}
// ---------------------------------------------------------------------------
// NBT helpers
// ---------------------------------------------------------------------------
enum {
TAG_END = 0,
TAG_BYTE = 1,
TAG_INT = 3,
TAG_LONG = 4,
TAG_STRING = 8,
TAG_LIST = 9,
TAG_COMPOUND = 10,
TAG_INT_ARRAY = 11,
TAG_LONG_ARRAY = 12
};
static void nbt_write_string(buf *b, const char *s) {
size_t len = strlen(s);
if (len > 0xFFFF) len = 0xFFFF;
buf_put_be16(b, (uint16_t)len);
buf_append(b, (const uint8_t *)s, len);
}
static void nbt_write_tag_header(buf *b, uint8_t tag, const char *name) {
buf_put_u8(b, tag);
nbt_write_string(b, name);
}
static void nbt_write_byte(buf *b, const char *name, uint8_t value) {
nbt_write_tag_header(b, TAG_BYTE, name);
buf_put_u8(b, value);
}
static void nbt_write_int(buf *b, const char *name, int32_t value) {
nbt_write_tag_header(b, TAG_INT, name);
buf_put_be32(b, (uint32_t)value);
}
static void nbt_write_long(buf *b, const char *name, int64_t value) {
nbt_write_tag_header(b, TAG_LONG, name);
buf_put_be64(b, (uint64_t)value);
}
static void nbt_write_string_tag(buf *b, const char *name, const char *value) {
nbt_write_tag_header(b, TAG_STRING, name);
nbt_write_string(b, value);
}
static void nbt_write_int_array(buf *b, const char *name, const int32_t *vals, size_t count) {
nbt_write_tag_header(b, TAG_INT_ARRAY, name);
buf_put_be32(b, (uint32_t)count);
for (size_t i = 0; i < count; ++i) {
buf_put_be32(b, (uint32_t)vals[i]);
}
}
static void nbt_write_long_array(buf *b, const char *name, const int64_t *vals, size_t count) {
nbt_write_tag_header(b, TAG_LONG_ARRAY, name);
buf_put_be32(b, (uint32_t)count);
for (size_t i = 0; i < count; ++i) {
buf_put_be64(b, (uint64_t)vals[i]);
}
}
static void nbt_start_compound(buf *b, const char *name) {
nbt_write_tag_header(b, TAG_COMPOUND, name);
}
static void nbt_end_compound(buf *b) {
buf_put_u8(b, TAG_END);
}
static void nbt_start_list(buf *b, const char *name, uint8_t tag_type, int32_t length) {
nbt_write_tag_header(b, TAG_LIST, name);
buf_put_u8(b, tag_type);
buf_put_be32(b, (uint32_t)length);
}
// ---------------------------------------------------------------------------
// Bit packing helpers
// ---------------------------------------------------------------------------
static int64_t to_signed64(uint64_t v) {
if (v <= INT64_MAX) return (int64_t)v;
return -(int64_t)((~v) + 1);
}
static void pack_bits(const uint16_t *indices, size_t count, int bits_per_value, int64_t *out_longs, size_t out_count) {
memset(out_longs, 0, out_count * sizeof(int64_t));
for (size_t idx = 0; idx < count; ++idx) {
uint64_t value = indices[idx];
size_t bit_index = idx * (size_t)bits_per_value;
size_t long_id = bit_index / 64;
size_t offset = bit_index % 64;
uint64_t *target = (uint64_t *)&out_longs[long_id];
*target |= value << offset;
int spill = (int)(offset + bits_per_value - 64);
if (spill > 0 && long_id + 1 < out_count) {
uint64_t *next = (uint64_t *)&out_longs[long_id + 1];
*next |= value >> (bits_per_value - spill);
}
}
}
// ---------------------------------------------------------------------------
// Chunk -> NBT helpers
// ---------------------------------------------------------------------------
static void pack_heightmap(const chunk_data *chunk, int64_t *out_longs, size_t out_count) {
uint16_t values[CHUNK_SIZE * CHUNK_SIZE];
for (int z = 0; z < CHUNK_SIZE; ++z) {
for (int x = 0; x < CHUNK_SIZE; ++x) {
values[x + z * CHUNK_SIZE] = chunk->heightmap[x][z];
}
}
pack_bits(values, CHUNK_SIZE * CHUNK_SIZE, 9, out_longs, out_count);
for (size_t i = 0; i < out_count; ++i) {
out_longs[i] = to_signed64((uint64_t)out_longs[i]);
}
}
static int section_has_blocks(const chunk_data *chunk, int section_y) {
int y_base = section_y * 16;
for (int y = 0; y < 16; ++y) {
int gy = y_base + y;
if (gy >= CHUNK_HEIGHT) break;
for (int x = 0; x < CHUNK_SIZE; ++x) {
for (int z = 0; z < CHUNK_SIZE; ++z) {
if (chunk->blocks[gy][x][z] != BLOCK_AIR) {
return 1;
}
}
}
}
return 0;
}
static void write_palette_entry(buf *b, const block_state *state) {
nbt_write_string_tag(b, "Name", state->name);
if (state->prop_count > 0) {
nbt_start_compound(b, "Properties");
for (size_t i = 0; i < state->prop_count; ++i) {
nbt_write_string_tag(b, state->props[i].key, state->props[i].value);
}
nbt_end_compound(b);
}
buf_put_u8(b, TAG_END); // end of this palette entry compound
}
static void write_section(buf *b, const chunk_data *chunk, int section_y) {
int palette_index[16];
for (size_t i = 0; i < 16; ++i) palette_index[i] = -1;
const block_state *palette_states[16];
uint16_t block_indices[4096];
size_t palette_len = 0;
int y_base = section_y * 16;
int idx = 0;
for (int y = 0; y < 16; ++y) {
int gy = y_base + y;
if (gy >= CHUNK_HEIGHT) break;
for (int z = 0; z < CHUNK_SIZE; ++z) {
for (int x = 0; x < CHUNK_SIZE; ++x) {
uint16_t bid = chunk->blocks[gy][x][z];
if (palette_index[bid] == -1) {
palette_index[bid] = (int)palette_len;
palette_states[palette_len] = get_block_state(bid);
palette_len++;
}
block_indices[idx++] = (uint16_t)palette_index[bid];
}
}
}
int bits = 4;
if (palette_len > 1) {
int needed = (int)ceil(log2((double)palette_len));
if (needed > bits) bits = needed;
}
size_t packed_count = ((size_t)idx * (size_t)bits + 63) / 64;
int64_t *packed = (int64_t *)calloc(packed_count, sizeof(int64_t));
if (!packed) return;
pack_bits(block_indices, idx, bits, packed, packed_count);
nbt_write_byte(b, "Y", (uint8_t)section_y);
nbt_write_long_array(b, "BlockStates", packed, packed_count);
nbt_start_list(b, "Palette", TAG_COMPOUND, (int32_t)palette_len);
for (size_t i = 0; i < palette_len; ++i) {
write_palette_entry(b, palette_states[i]);
}
// Palette entries already include their end tags; list is fixed-length so no end marker here.
// Lighting arrays (all zero)
uint8_t light[2048];
memset(light, 0, sizeof(light));
nbt_write_tag_header(b, 7, "BlockLight");
buf_put_be32(b, (uint32_t)sizeof(light));
buf_append(b, light, sizeof(light));
nbt_write_tag_header(b, 7, "SkyLight");
buf_put_be32(b, (uint32_t)sizeof(light));
buf_append(b, light, sizeof(light));
nbt_end_compound(b);
free(packed);
}
static void build_chunk_nbt(const chunk_data *chunk, buf *out) {
int32_t biomes[256];
for (int i = 0; i < 256; ++i) biomes[i] = 1; // Plains biome
int64_t heightmap[36];
pack_heightmap(chunk, heightmap, 36);
nbt_start_compound(out, "");
nbt_write_int(out, "DataVersion", 2586);
nbt_start_compound(out, "Level");
nbt_write_string_tag(out, "Status", "full");
nbt_write_long(out, "InhabitedTime", 0);
nbt_write_long(out, "LastUpdate", 0);
nbt_write_int(out, "xPos", chunk->chunk_x);
nbt_write_int(out, "zPos", chunk->chunk_z);
nbt_write_byte(out, "isLightOn", 1);
nbt_start_compound(out, "Heightmaps");
nbt_write_long_array(out, "MOTION_BLOCKING", heightmap, 36);
nbt_end_compound(out);
nbt_write_int_array(out, "Biomes", biomes, 256);
// Sections
int section_count = 0;
for (int sy = 0; sy < CHUNK_HEIGHT / 16; ++sy) {
if (section_has_blocks(chunk, sy)) section_count++;
}
nbt_start_list(out, "Sections", TAG_COMPOUND, section_count);
for (int sy = 0; sy < CHUNK_HEIGHT / 16; ++sy) {
if (!section_has_blocks(chunk, sy)) continue;
write_section(out, chunk, sy);
}
// Empty entity lists
nbt_start_list(out, "Entities", TAG_COMPOUND, 0);
nbt_start_list(out, "TileEntities", TAG_COMPOUND, 0);
nbt_start_list(out, "TileTicks", TAG_COMPOUND, 0);
nbt_end_compound(out); // Level
nbt_end_compound(out); // root
}
static int compress_chunk(const buf *input, buf *output) {
uLongf bound = compressBound((uLong)input->len);
buf_reserve(output, bound);
uLongf dest_len = (uLongf)output->cap;
int res = compress2(output->data, &dest_len, input->data, (uLong)input->len, Z_BEST_SPEED);
if (res != Z_OK) return -1;
output->len = dest_len;
return 0;
}
// ---------------------------------------------------------------------------
// Region file aggregation
// ---------------------------------------------------------------------------
typedef struct {
uint8_t *data;
size_t size;
} chunk_blob;
typedef struct {
int region_x;
int region_z;
chunk_blob chunks[32 * 32];
uint8_t present[32 * 32];
} region_accum;
static region_accum *find_or_add_region(region_accum **list, size_t *count, size_t *cap, int rx, int rz) {
for (size_t i = 0; i < *count; ++i) {
if ((*list)[i].region_x == rx && (*list)[i].region_z == rz) return &(*list)[i];
}
if (*count >= *cap) {
size_t new_cap = *cap ? *cap * 2 : 8;
region_accum *new_list = (region_accum *)realloc(*list, new_cap * sizeof(region_accum));
if (!new_list) return NULL;
*list = new_list;
*cap = new_cap;
}
region_accum *reg = &(*list)[(*count)++];
memset(reg, 0, sizeof(*reg));
reg->region_x = rx;
reg->region_z = rz;
return reg;
}
static void free_regions(region_accum *regions, size_t count) {
for (size_t i = 0; i < count; ++i) {
for (int j = 0; j < 32 * 32; ++j) {
free(regions[i].chunks[j].data);
}
}
free(regions);
}
static void write_region_file(const char *out_dir, const region_accum *reg) {
uint8_t offsets[4096];
uint8_t timestamps[4096];
memset(offsets, 0, sizeof(offsets));
memset(timestamps, 0, sizeof(timestamps));
buf body = {0};
uint32_t sector = 2;
time_t now = time(NULL);
for (int idx = 0; idx < 32 * 32; ++idx) {
if (!reg->present[idx]) continue;
const chunk_blob *cb = &reg->chunks[idx];
if (!cb->data || cb->size == 0) continue;
uint32_t length = (uint32_t)(cb->size + 1);
uint32_t padding = (4096 - ((length + 4) % 4096)) % 4096;
uint32_t total_len = length + 4 + padding;
uint32_t sectors = total_len / 4096;
// offsets
offsets[idx * 4 + 0] = (uint8_t)((sector >> 16) & 0xFF);
offsets[idx * 4 + 1] = (uint8_t)((sector >> 8) & 0xFF);
offsets[idx * 4 + 2] = (uint8_t)(sector & 0xFF);
offsets[idx * 4 + 3] = (uint8_t)sectors;
// timestamps
uint32_t ts = (uint32_t)now;
timestamps[idx * 4 + 0] = (uint8_t)((ts >> 24) & 0xFF);
timestamps[idx * 4 + 1] = (uint8_t)((ts >> 16) & 0xFF);
timestamps[idx * 4 + 2] = (uint8_t)((ts >> 8) & 0xFF);
timestamps[idx * 4 + 3] = (uint8_t)(ts & 0xFF);
// payload: length (4 bytes), compression type (1), data, padding
buf_reserve(&body, total_len);
buf_put_be32(&body, length);
buf_put_u8(&body, 2); // compression type 2 = zlib
buf_append(&body, cb->data, cb->size);
if (padding) {
uint8_t zeros[4096] = {0};
buf_append(&body, zeros, padding);
}
sector += sectors;
}
buf file = {0};
buf_reserve(&file, 4096 * 2 + body.len);
buf_append(&file, offsets, sizeof(offsets));
buf_append(&file, timestamps, sizeof(timestamps));
buf_append(&file, body.data, body.len);
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/r.%d.%d.mca", out_dir, reg->region_x, reg->region_z);
FILE *fp = fopen(path, "wb");
if (fp) {
fwrite(file.data, 1, file.len, fp);
fclose(fp);
} else {
fprintf(stderr, "Failed to write region %s\n", path);
}
free(file.data);
free(body.data);
}
static void write_regions(const char *out_dir, region_accum *regions, size_t region_count) {
for (size_t i = 0; i < region_count; ++i) {
write_region_file(out_dir, &regions[i]);
}
}
static void usage(const char *prog) {
fprintf(stderr,
"Usage: %s [--radius R] [--center-x X --center-z Z] [--min-x MX --max-x MX --min-z MZ --max-z MZ]\n"
" [--threads N] [--seed S] [--sea-level L] [--snow-line H] [--format mca|bin] [--trails] [--out DIR]\n",
prog);
}
static long parse_long(const char *s) {
char *end = NULL;
errno = 0;
long v = strtol(s, &end, 10);
if (errno != 0 || !end || *end != '\0') {
fprintf(stderr, "Invalid number: %s\n", s);
exit(1);
}
return v;
}
static int ensure_dir(const char *path) {
char tmp[PATH_MAX];
size_t len = strlen(path);
if (len == 0 || len >= sizeof(tmp)) return -1;
strcpy(tmp, path);
for (size_t i = 1; i <= len; ++i) {
if (tmp[i] == '/' || tmp[i] == '\0') {
char saved = tmp[i];
tmp[i] = '\0';
if (strlen(tmp) > 0) {
if (mkdir(tmp, 0777) != 0 && errno != EEXIST) {
if (errno != EEXIST) return -1;
}
}
tmp[i] = saved;
}
}
return 0;
}
static void write_chunk_file(const char *out_dir, const chunk_data *chunk) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/chunk_%d_%d.bin", out_dir, chunk->chunk_x, chunk->chunk_z);
FILE *fp = fopen(path, "wb");
if (!fp) {
fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
return;
}
int32_t header[2] = {chunk->chunk_x, chunk->chunk_z};
fwrite(header, sizeof(header[0]), 2, fp);
fwrite(chunk->heightmap, sizeof(uint16_t), CHUNK_SIZE * CHUNK_SIZE, fp);
fwrite(chunk->blocks, sizeof(uint16_t), CHUNK_HEIGHT * CHUNK_SIZE * CHUNK_SIZE, fp);
fclose(fp);
}
static chunk_coord *build_job_list(int min_x, int max_x, int min_z, int max_z, size_t *out_count) {
if (min_x > max_x || min_z > max_z) return NULL;
size_t count = (size_t)(max_x - min_x + 1) * (size_t)(max_z - min_z + 1);
chunk_coord *jobs = (chunk_coord *)malloc(count * sizeof(chunk_coord));
if (!jobs) return NULL;
size_t idx = 0;
for (int x = min_x; x <= max_x; ++x) {
for (int z = min_z; z <= max_z; ++z) {
jobs[idx].x = x;
jobs[idx].z = z;
++idx;
}
}
*out_count = count;
return jobs;
}
static void *worker_fn(void *ptr) {
worker_args *args = (worker_args *)ptr;
worldgen_ctx ctx;
worldgen_init(&ctx, args->world_seed, args->sea_level, args->snow_line);
ctx.enable_trails = args->enable_trails;
while (1) {
size_t idx = atomic_fetch_add(&args->queue->next_index, 1);
if (idx >= args->queue->count) break;
chunk_coord job = args->queue->items[idx];
chunk_data chunk;
worldgen_generate_chunk(&ctx, job.x, job.z, &chunk);
if (args->results) {
pthread_mutex_lock(&args->results->mutex);
size_t result_idx = atomic_fetch_add(&args->results->count, 1);
if (result_idx < args->results->capacity) {
args->results->chunks[result_idx].chunk_x = chunk.chunk_x;
args->results->chunks[result_idx].chunk_z = chunk.chunk_z;
memcpy(&args->results->chunks[result_idx].data, &chunk, sizeof(chunk_data));
}
pthread_mutex_unlock(&args->results->mutex);
} else {
write_chunk_file(args->out_dir, &chunk);
}
size_t done_now = atomic_fetch_add(&args->queue->done, 1) + 1;
if (args->log_mu) {
pthread_mutex_lock(args->log_mu);
double pct = (double)done_now * 100.0 / (double)args->queue->count;
fprintf(stderr, "\rProgress: %zu/%zu (%.1f%%) last (%d,%d)", done_now, args->queue->count, pct, job.x, job.z);
fflush(stderr);
pthread_mutex_unlock(args->log_mu);
}
}
return NULL;
}
int main(int argc, char **argv) {
int have_rect = 0;
int min_x = 0, max_x = 0, min_z = 0, max_z = 0;
int center_x = 0, center_z = 0;
int radius = 1;
int threads = (int)sysconf(_SC_NPROCESSORS_ONLN);
if (threads < 1) threads = 4;
int world_seed = 123456;
int sea_level = 70;
int snow_line = INT_MIN;
int enable_trails = 0;
const char *out_dir = "output";
output_format format = FORMAT_MCA;
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--radius") == 0 && i + 1 < argc) {
radius = (int)parse_long(argv[++i]);
} else if (strcmp(argv[i], "--center-x") == 0 && i + 1 < argc) {
center_x = (int)parse_long(argv[++i]);
} else if (strcmp(argv[i], "--center-z") == 0 && i + 1 < argc) {
center_z = (int)parse_long(argv[++i]);
} else if (strcmp(argv[i], "--min-x") == 0 && i + 1 < argc) {
min_x = (int)parse_long(argv[++i]);
have_rect = 1;
} else if (strcmp(argv[i], "--max-x") == 0 && i + 1 < argc) {
max_x = (int)parse_long(argv[++i]);
have_rect = 1;
} else if (strcmp(argv[i], "--min-z") == 0 && i + 1 < argc) {
min_z = (int)parse_long(argv[++i]);
have_rect = 1;
} else if (strcmp(argv[i], "--max-z") == 0 && i + 1 < argc) {
max_z = (int)parse_long(argv[++i]);
have_rect = 1;
} else if (strcmp(argv[i], "--threads") == 0 && i + 1 < argc) {
threads = (int)parse_long(argv[++i]);
} else if (strcmp(argv[i], "--seed") == 0 && i + 1 < argc) {
world_seed = (int)parse_long(argv[++i]);
} else if (strcmp(argv[i], "--sea-level") == 0 && i + 1 < argc) {
sea_level = (int)parse_long(argv[++i]);
} else if (strcmp(argv[i], "--snow-line") == 0 && i + 1 < argc) {
snow_line = (int)parse_long(argv[++i]);
} else if (strcmp(argv[i], "--format") == 0 && i + 1 < argc) {
const char *f = argv[++i];
if (strcmp(f, "mca") == 0) {
format = FORMAT_MCA;
} else if (strcmp(f, "bin") == 0) {
format = FORMAT_BIN;
} else {
fprintf(stderr, "Unknown format: %s (use mca or bin)\n", f);
return 1;
}
} else if (strcmp(argv[i], "--trails") == 0) {
enable_trails = 1;
} else if ((strcmp(argv[i], "--out") == 0 || strcmp(argv[i], "--output") == 0) && i + 1 < argc) {
out_dir = argv[++i];
} else if (strcmp(argv[i], "--help") == 0) {
usage(argv[0]);
return 0;
} else {
fprintf(stderr, "Unknown argument: %s\n", argv[i]);
usage(argv[0]);
return 1;
}
}
if (snow_line == INT_MIN) {
snow_line = sea_level + 38;
}
if (!have_rect) {
min_x = center_x - radius;
max_x = center_x + radius;
min_z = center_z - radius;
max_z = center_z + radius;
}
if (ensure_dir(out_dir) != 0) {
fprintf(stderr, "Failed to create output directory: %s\n", out_dir);
return 1;
}
size_t job_count = 0;
chunk_coord *jobs = build_job_list(min_x, max_x, min_z, max_z, &job_count);
if (!jobs || job_count == 0) {
fprintf(stderr, "No chunks to generate.\n");
free(jobs);
return 1;
}
job_queue queue = {.items = jobs, .count = job_count, .next_index = 0};
atomic_init(&queue.done, 0);
pthread_mutex_t log_mu = PTHREAD_MUTEX_INITIALIZER;
results_buffer results;
results.chunks = (completed_chunk *)malloc(sizeof(completed_chunk) * job_count);
results.capacity = job_count;
atomic_init(&results.count, 0);
results.mutex = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
if (threads < 1) threads = 1;
pthread_t *workers = (pthread_t *)malloc(sizeof(pthread_t) * (size_t)threads);
if (!workers) {
fprintf(stderr, "Failed to allocate threads array.\n");
free(results.chunks);
free(jobs);
return 1;
}
worker_args args = {
.queue = &queue,
.out_dir = out_dir,
.world_seed = world_seed,
.sea_level = sea_level,
.snow_line = snow_line,
.log_mu = &log_mu,
.enable_trails = enable_trails,
.results = &results};
for (int i = 0; i < threads; ++i) {
pthread_create(&workers[i], NULL, worker_fn, &args);
}
for (int i = 0; i < threads; ++i) {
pthread_join(workers[i], NULL);
}
fprintf(stderr, "\n");
size_t completed = atomic_load(&results.count);
if (format == FORMAT_BIN) {
for (size_t i = 0; i < completed; ++i) {
write_chunk_file(out_dir, &results.chunks[i].data);
}
fprintf(stdout, "Generated %zu chunk(s) into %s (raw bin) using %d thread(s).\n", completed, out_dir, threads);
} else {
region_accum *regions = NULL;
size_t region_count = 0, region_cap = 0;
for (size_t i = 0; i < completed; ++i) {
const completed_chunk *cc = &results.chunks[i];
buf nbt = {0};
build_chunk_nbt(&cc->data, &nbt);
buf compressed = {0};
if (compress_chunk(&nbt, &compressed) != 0) {
free(nbt.data);
free(compressed.data);
continue;
}
free(nbt.data);
int cx = cc->chunk_x;
int cz = cc->chunk_z;
int region_x = (cx >= 0) ? cx / 32 : ((cx - 31) / 32);
int region_z = (cz >= 0) ? cz / 32 : ((cz - 31) / 32);
int local_x = cx - region_x * 32;
int local_z = cz - region_z * 32;
int index = local_x + local_z * 32;
region_accum *reg = find_or_add_region(&regions, &region_count, &region_cap, region_x, region_z);
if (!reg) {
free(compressed.data);
continue;
}
if (reg->present[index]) {
free(reg->chunks[index].data);
}
reg->present[index] = 1;
reg->chunks[index].data = compressed.data;
reg->chunks[index].size = compressed.len;
}
write_regions(out_dir, regions, region_count);
free_regions(regions, region_count);
fprintf(stdout, "Generated %zu chunk(s) into %s (MCA) using %d thread(s).\n", completed, out_dir, threads);
}
free(results.chunks);
free(workers);
free(jobs);
return 0;
}

180
worldgen-c/src/noise.c Normal file
View File

@@ -0,0 +1,180 @@
#include "noise.h"
#include <math.h>
#include <stddef.h>
// Simplex noise implementation (Stefan Gustavson public domain style).
// Enough for terrain-like smooth noise; deterministic per seed.
static const int grad3[12][3] = {
{1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0},
{1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1},
{0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}
};
static double dot(const int *g, double x, double y) {
return g[0] * x + g[1] * y;
}
static double dot3(const int *g, double x, double y, double z) {
return g[0] * x + g[1] * y + g[2] * z;
}
static uint32_t lcg(uint32_t seed) {
return seed * 1664525u + 1013904223u;
}
void simplex_init(simplex_noise *noise, uint32_t seed) {
for (int i = 0; i < 256; ++i) {
noise->perm[i] = i;
}
uint32_t s = seed;
for (int i = 255; i > 0; --i) {
s = lcg(s);
int j = (s + 31) % (i + 1);
int tmp = noise->perm[i];
noise->perm[i] = noise->perm[j];
noise->perm[j] = tmp;
}
for (int i = 0; i < 256; ++i) {
noise->perm[256 + i] = noise->perm[i];
}
}
double simplex_noise2(simplex_noise *noise, double xin, double yin) {
const double F2 = 0.5 * (sqrt(3.0) - 1.0);
const double G2 = (3.0 - sqrt(3.0)) / 6.0;
double s = (xin + yin) * F2;
int i = (int)floor(xin + s);
int j = (int)floor(yin + s);
double t = (i + j) * G2;
double X0 = i - t;
double Y0 = j - t;
double x0 = xin - X0;
double y0 = yin - Y0;
int i1, j1;
if (x0 > y0) {
i1 = 1; j1 = 0;
} else {
i1 = 0; j1 = 1;
}
double x1 = x0 - i1 + G2;
double y1 = y0 - j1 + G2;
double x2 = x0 - 1.0 + 2.0 * G2;
double y2 = y0 - 1.0 + 2.0 * G2;
int ii = i & 255;
int jj = j & 255;
int gi0 = noise->perm[ii + noise->perm[jj]] % 12;
int gi1 = noise->perm[ii + i1 + noise->perm[jj + j1]] % 12;
int gi2 = noise->perm[ii + 1 + noise->perm[jj + 1]] % 12;
double n0 = 0.0, n1 = 0.0, n2 = 0.0;
double t0 = 0.5 - x0 * x0 - y0 * y0;
if (t0 > 0) {
t0 *= t0;
n0 = t0 * t0 * dot(grad3[gi0], x0, y0);
}
double t1 = 0.5 - x1 * x1 - y1 * y1;
if (t1 > 0) {
t1 *= t1;
n1 = t1 * t1 * dot(grad3[gi1], x1, y1);
}
double t2 = 0.5 - x2 * x2 - y2 * y2;
if (t2 > 0) {
t2 *= t2;
n2 = t2 * t2 * dot(grad3[gi2], x2, y2);
}
return 70.0 * (n0 + n1 + n2);
}
double simplex_noise3(simplex_noise *noise, double xin, double yin, double zin) {
const double F3 = 1.0 / 3.0;
const double G3 = 1.0 / 6.0;
double s = (xin + yin + zin) * F3;
int i = (int)floor(xin + s);
int j = (int)floor(yin + s);
int k = (int)floor(zin + s);
double t = (i + j + k) * G3;
double X0 = i - t;
double Y0 = j - t;
double Z0 = k - t;
double x0 = xin - X0;
double y0 = yin - Y0;
double z0 = zin - Z0;
int i1, j1, k1;
int i2, j2, k2;
if (x0 >= y0) {
if (y0 >= z0) {
i1 = 1; j1 = 0; k1 = 0;
i2 = 1; j2 = 1; k2 = 0;
} else if (x0 >= z0) {
i1 = 1; j1 = 0; k1 = 0;
i2 = 1; j2 = 0; k2 = 1;
} else {
i1 = 0; j1 = 0; k1 = 1;
i2 = 1; j2 = 0; k2 = 1;
}
} else {
if (y0 < z0) {
i1 = 0; j1 = 0; k1 = 1;
i2 = 0; j2 = 1; k2 = 1;
} else if (x0 < z0) {
i1 = 0; j1 = 1; k1 = 0;
i2 = 0; j2 = 1; k2 = 1;
} else {
i1 = 0; j1 = 1; k1 = 0;
i2 = 1; j2 = 1; k2 = 0;
}
}
double x1 = x0 - i1 + G3;
double y1 = y0 - j1 + G3;
double z1 = z0 - k1 + G3;
double x2 = x0 - i2 + 2.0 * G3;
double y2 = y0 - j2 + 2.0 * G3;
double z2 = z0 - k2 + 2.0 * G3;
double x3 = x0 - 1.0 + 3.0 * G3;
double y3 = y0 - 1.0 + 3.0 * G3;
double z3 = z0 - 1.0 + 3.0 * G3;
int ii = i & 255;
int jj = j & 255;
int kk = k & 255;
int gi0 = noise->perm[ii + noise->perm[jj + noise->perm[kk]]] % 12;
int gi1 = noise->perm[ii + i1 + noise->perm[jj + j1 + noise->perm[kk + k1]]] % 12;
int gi2 = noise->perm[ii + i2 + noise->perm[jj + j2 + noise->perm[kk + k2]]] % 12;
int gi3 = noise->perm[ii + 1 + noise->perm[jj + 1 + noise->perm[kk + 1]]] % 12;
double n0 = 0.0, n1 = 0.0, n2 = 0.0, n3 = 0.0;
double t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
if (t0 > 0) {
t0 *= t0;
n0 = t0 * t0 * dot3(grad3[gi0], x0, y0, z0);
}
double t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
if (t1 > 0) {
t1 *= t1;
n1 = t1 * t1 * dot3(grad3[gi1], x1, y1, z1);
}
double t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
if (t2 > 0) {
t2 *= t2;
n2 = t2 * t2 * dot3(grad3[gi2], x2, y2, z2);
}
double t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
if (t3 > 0) {
t3 *= t3;
n3 = t3 * t3 * dot3(grad3[gi3], x3, y3, z3);
}
return 32.0 * (n0 + n1 + n2 + n3);
}

1839
worldgen-c/src/worldgen.c Normal file

File diff suppressed because it is too large Load Diff