From 4431b86dab911361477f7f8793a98cf170dd61b4 Mon Sep 17 00:00:00 2001 From: chelsea Date: Mon, 1 Dec 2025 17:00:57 -0600 Subject: [PATCH] Add worldgen sources --- Makefile | 22 ++ bin/worldgen | Bin 0 -> 51272 bytes include/noise.h | 14 + include/worldgen.h | 41 +++ src/main.c | 799 +++++++++++++++++++++++++++++++++++++++++++++ src/main.o | Bin 0 -> 31224 bytes src/noise.c | 180 ++++++++++ src/noise.o | Bin 0 -> 5840 bytes src/worldgen.c | 463 ++++++++++++++++++++++++++ src/worldgen.o | Bin 0 -> 15528 bytes 10 files changed, 1519 insertions(+) create mode 100644 Makefile create mode 100755 bin/worldgen create mode 100644 include/noise.h create mode 100644 include/worldgen.h create mode 100644 src/main.c create mode 100644 src/main.o create mode 100644 src/noise.c create mode 100644 src/noise.o create mode 100644 src/worldgen.c create mode 100644 src/worldgen.o diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8e6a98c --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +CC ?= gcc +CFLAGS ?= -O3 -march=native -std=c11 -Wall -Wextra -pedantic -Iinclude +LDFLAGS ?= -lm -pthread -lz + +SRC := src/main.c src/worldgen.c src/noise.c +OBJ := $(SRC:.c=.o) + +BIN_DIR := bin +TARGET := $(BIN_DIR)/worldgen + +all: $(TARGET) + +$(TARGET): $(OBJ) | $(BIN_DIR) + $(CC) $(CFLAGS) $(OBJ) -o $@ $(LDFLAGS) + +$(BIN_DIR): + mkdir -p $(BIN_DIR) + +clean: + rm -f $(OBJ) $(TARGET) + +.PHONY: all clean diff --git a/bin/worldgen b/bin/worldgen new file mode 100755 index 0000000000000000000000000000000000000000..0988b3c013c257b1396de98cf6a11bfdf1b220f5 GIT binary patch literal 51272 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℞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 Ru3LWFI*u_SqCiAa`oBa=v?l-n literal 0 HcmV?d00001 diff --git a/include/noise.h b/include/noise.h new file mode 100644 index 0000000..e7dc1ad --- /dev/null +++ b/include/noise.h @@ -0,0 +1,14 @@ +#ifndef WORLDGEN_NOISE_H +#define WORLDGEN_NOISE_H + +#include + +typedef struct { + int perm[512]; +} simplex_noise; + +void simplex_init(simplex_noise *noise, uint32_t seed); +double simplex_noise2(simplex_noise *noise, double x, double y); +double simplex_noise3(simplex_noise *noise, double x, double y, double z); + +#endif diff --git a/include/worldgen.h b/include/worldgen.h new file mode 100644 index 0000000..007059a --- /dev/null +++ b/include/worldgen.h @@ -0,0 +1,41 @@ +#ifndef WORLDGEN_H +#define WORLDGEN_H + +#include +#include +#include "noise.h" + +#define CHUNK_SIZE 16 +#define CHUNK_HEIGHT 256 + +enum BlockId { + BLOCK_BEDROCK = 0, + BLOCK_STONE = 1, + BLOCK_DIRT = 2, + BLOCK_GRASS = 3, + BLOCK_WATER = 4, + BLOCK_AIR = 5, + BLOCK_OAK_LOG = 6, + BLOCK_OAK_LEAVES = 7, + BLOCK_BIRCH_LOG = 8, + BLOCK_BIRCH_LEAVES = 9, + BLOCK_COAL = 10 +}; + +typedef struct { + int chunk_x; + int chunk_z; + uint16_t heightmap[CHUNK_SIZE][CHUNK_SIZE]; + uint16_t blocks[CHUNK_HEIGHT][CHUNK_SIZE][CHUNK_SIZE]; +} chunk_data; + +typedef struct { + simplex_noise noise; + int sea_level; + int world_seed; +} worldgen_ctx; + +void worldgen_init(worldgen_ctx *ctx, int world_seed, int sea_level); +void worldgen_generate_chunk(worldgen_ctx *ctx, int chunk_x, int chunk_z, chunk_data *out); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..31cbccc --- /dev/null +++ b/src/main.c @@ -0,0 +1,799 @@ +#include "worldgen.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; + pthread_mutex_t *log_mu; + 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} +}; + +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 = ®->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, ®ions[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] [--format mca|bin] [--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); + 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; + 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], "--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], "--out") == 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 (!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, + .log_mu = &log_mu, + .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(®ions, ®ion_count, ®ion_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; +} diff --git a/src/main.o b/src/main.o new file mode 100644 index 0000000000000000000000000000000000000000..a2b69814a595191ea8a4ef8b621ed90109109fcd GIT binary patch literal 31224 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 literal 0 HcmV?d00001 diff --git a/src/noise.c b/src/noise.c new file mode 100644 index 0000000..45bd0d9 --- /dev/null +++ b/src/noise.c @@ -0,0 +1,180 @@ +#include "noise.h" +#include +#include + +// 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); +} diff --git a/src/noise.o b/src/noise.o new file mode 100644 index 0000000000000000000000000000000000000000..a886ccb80d1dfa4685985555dc224097b1d6ae06 GIT binary patch literal 5840 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 X;~1&uM>mJk_o;;XwL_O=s{X$KN@_Kj literal 0 HcmV?d00001 diff --git a/src/worldgen.c b/src/worldgen.c new file mode 100644 index 0000000..a9d8cb7 --- /dev/null +++ b/src/worldgen.c @@ -0,0 +1,463 @@ +#include "worldgen.h" + +#include +#include +#include +#include +#include + +typedef struct { + int height; + int water_surface; + int has_water; +} 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; + +// --------------------------------------------------------------------------- +// 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); +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- +static inline double clamp01(double v) { + if (v < 0.0) return 0.0; + if (v > 1.0) return 1.0; + return v; +} + +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 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 height = ctx->sea_level - 5; + height += simplex_noise2(&ctx->noise, warp2_x * 0.0005, warp2_z * 0.0005) * 80.0; + height += simplex_noise2(&ctx->noise, warp2_x * 0.002, warp2_z * 0.002) * 40.0; + height += simplex_noise2(&ctx->noise, warp2_x * 0.005, warp2_z * 0.005) * 25.0; + height += simplex_noise2(&ctx->noise, warp2_x * 0.01, warp2_z * 0.01) * 15.0; + height += simplex_noise2(&ctx->noise, warp2_x * 0.02, warp2_z * 0.02) * 5.0; + height += simplex_noise2(&ctx->noise, warp2_x * 0.05, warp2_z * 0.05) * 2.0; + height += simplex_noise2(&ctx->noise, warp2_x * 0.1, warp2_z * 0.1) * 1.0; + 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; + } else if (data.height < ctx->sea_level) { + data.has_water = 1; + data.water_surface = ctx->sea_level; + } else { + data.has_water = 0; + data.water_surface = 0; + } + return data; +} + +// --------------------------------------------------------------------------- +// Ore generation (coal seams) +// --------------------------------------------------------------------------- +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 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, const char *biome) { + (void)biome; + if (check_seam_generic(ctx, x, y, z, 12, 5, 0.02, 2.0, 0.03, 1.0, -0.7)) return 1; + if (check_seam_generic(ctx, x, y, z, 32, 4, 0.02, 2.0, 0.03, 1.0, -0.7)) return 1; + if (check_seam_generic(ctx, x, y, z, 85, 3, 0.02, 2.0, 0.03, 1.0, -0.7)) return 1; + if (check_seam_generic(ctx, x, y, z, 45, 2, 0.015, 1.5, 0.04, 0.5, -0.6)) return 1; + if (check_seam_generic(ctx, x, y, z, 25, 3, 0.015, 1.5, 0.04, 0.5, -0.6)) return 1; + if (check_flat_ceiling_seam(ctx, x, y, z, column_height, 5, 1, -0.3)) return 1; + if (check_terrain_relative_seam(ctx, x, y, z, column_height, 8, 2, 0.03, 0.8, -0.65)) return 1; + return 0; +} + +// --------------------------------------------------------------------------- +// Terrain block generation +// --------------------------------------------------------------------------- +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, "appalachian")) { + return BLOCK_COAL; + } + return BLOCK_STONE; + } + if (y < data.height) return BLOCK_DIRT; + if (y == data.height) return BLOCK_GRASS; + 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 int can_place_tree(worldgen_ctx *ctx, int x, int y, int z, int height) { + int ground_block = sample_block(ctx, x, y - 1, z); + if (ground_block != BLOCK_GRASS) return 0; + for (int cy = y; cy < y + height + 3 && cy < CHUNK_HEIGHT; ++cy) { + if (sample_block(ctx, x, cy, z) != BLOCK_AIR) return 0; + } + return 1; +} + +static void generate_oak_tree(worldgen_ctx *ctx, int x, int y, int z, int variation, rng_state *rng, block_list *out) { + (void)ctx; + int trunk_height = 4 + variation + rng_range_inclusive(rng, 0, 2); + for (int dy = 0; dy < trunk_height; ++dy) { + block_list_push(out, x, y + dy, z, BLOCK_OAK_LOG); + } + int crown_start = y + trunk_height - 2; + int crown_top = y + trunk_height + 2; + for (int cy = crown_start; cy < crown_top; ++cy) { + int dist = abs(cy - (crown_start + 2)); + int radius = 1; + if (dist == 0 || dist == 1) radius = 2; + for (int dx = -radius; dx <= radius; ++dx) { + for (int dz = -radius; dz <= radius; ++dz) { + if (abs(dx) == radius && abs(dz) == radius) { + if (rng_next_f64(rng) > 0.3) continue; + } + if (dx == 0 && dz == 0 && cy < y + trunk_height) continue; + block_list_push(out, x + dx, cy, z + dz, BLOCK_OAK_LEAVES); + } + } + } +} + +static void generate_birch_tree(worldgen_ctx *ctx, int x, int y, int z, rng_state *rng, block_list *out) { + (void)ctx; + int trunk_height = 5 + rng_range_inclusive(rng, 0, 3); + for (int dy = 0; dy < trunk_height; ++dy) { + block_list_push(out, x, y + dy, z, BLOCK_BIRCH_LOG); + } + int crown_start = y + trunk_height - 1; + int crown_top = y + trunk_height + 2; + for (int cy = crown_start; cy < crown_top; ++cy) { + int distance_from_top = crown_top - cy; + int radius = (distance_from_top <= 1) ? 1 : 2; + for (int dx = -radius; dx <= radius; ++dx) { + for (int dz = -radius; dz <= radius; ++dz) { + if (abs(dx) + abs(dz) > radius + 1) continue; + if (dx == 0 && dz == 0 && cy < y + trunk_height) continue; + block_list_push(out, x + dx, cy, z + dz, BLOCK_BIRCH_LEAVES); + } + } + } +} + +static void generate_tree(worldgen_ctx *ctx, int x, int y, int z, const char *type, int variation, rng_state *rng, block_list *out) { + if (strcmp(type, "oak") == 0) { + if (variation < 0) variation = rng_range_inclusive(rng, 0, 2); + if (!can_place_tree(ctx, x, y, z, 4 + variation)) return; + generate_oak_tree(ctx, x, y, z, variation, rng, out); + } else if (strcmp(type, "birch") == 0) { + if (!can_place_tree(ctx, x, y, z, 6)) return; + generate_birch_tree(ctx, x, y, z, rng, out); + } +} + +static void generate_chunk_trees(worldgen_ctx *ctx, int chunk_x, int chunk_z, block_list *out) { + const int grid = 6; + const int margin = 3; + 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; + + 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 (altitude_factor <= 0.0) continue; + + double density = tree_density_mask(ctx, candidate_x, candidate_z); + double spawn_prob = 0.5 * (0.6 + 0.4 * density) * altitude_factor; + if (rng_next_f64(&rng) > spawn_prob) continue; + + const char *tree_type = "oak"; + if (data.height > ctx->sea_level + 35) { + tree_type = (rng_next_f64(&rng) < 0.7) ? "birch" : "oak"; + } else if (data.height > ctx->sea_level + 15) { + tree_type = (rng_next_f64(&rng) < 0.7) ? "oak" : "birch"; + } + + block_list tmp; + block_list_init(&tmp); + generate_tree(ctx, candidate_x, data.height + 1, candidate_z, tree_type, -1, &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); + } + } +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- +void worldgen_init(worldgen_ctx *ctx, int world_seed, int sea_level) { + ctx->world_seed = world_seed; + ctx->sea_level = sea_level; + 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; + + // 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]; + 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, chunk_x * CHUNK_SIZE + dx, y, chunk_z * CHUNK_SIZE + dz, cd.height, "appalachian")) { + id = BLOCK_COAL; + } else { + id = BLOCK_STONE; + } + } else if (y < cd.height) { + id = BLOCK_DIRT; + } else if (y == cd.height) { + id = BLOCK_GRASS; + } 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; + } + } + } + + // Tree overlay + block_list trees; + block_list_init(&trees); + generate_chunk_trees(ctx, chunk_x, chunk_z, &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); +} diff --git a/src/worldgen.o b/src/worldgen.o new file mode 100644 index 0000000000000000000000000000000000000000..f63913672edac36a1c25ed104688e33e92d9ca4f GIT binary patch literal 15528 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 XcQq9{Mhwe8m82+2gIx_g-TeO#Kd;e) literal 0 HcmV?d00001