aboutsummaryrefslogtreecommitdiff
path: root/src/shared/world.cc
diff options
context:
space:
mode:
authorNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-12 21:57:46 +1100
committerNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-12 21:57:46 +1100
commite4483eca01b48b943cd0461e24a74ae1a3139ed4 (patch)
treeed58c3c246e3af1af337697695d780aa31f6ad9a /src/shared/world.cc
parent1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (diff)
Update to most recent version (old initial commit)
Diffstat (limited to 'src/shared/world.cc')
-rw-r--r--src/shared/world.cc932
1 files changed, 0 insertions, 932 deletions
diff --git a/src/shared/world.cc b/src/shared/world.cc
deleted file mode 100644
index d56b7a8..0000000
--- a/src/shared/world.cc
+++ /dev/null
@@ -1,932 +0,0 @@
-#include "shared/world.hh"
-
-namespace shared {
-namespace world {
-
-// Because C++ doesn't allow us to iterate over an enum,
-// (or get the size of an enum, or get the name of an enum, etc) biomes defined
-// in the chunk::biome enum class have to be readded here. Alternatively we
-// could use some compiler hack library.
-constexpr std::array<enum shared::world::chunk::biome, 7> biome_enums{
- shared::world::chunk::biome::alpine, shared::world::chunk::biome::tundra,
- shared::world::chunk::biome::forest, shared::world::chunk::biome::plains,
- shared::world::chunk::biome::ocean, shared::world::chunk::biome::islands,
- shared::world::chunk::biome::desert};
-
-constexpr long get_biome_index(const enum shared::world::chunk::biome biome) noexcept {
- return std::distance(std::begin(biome_enums),
- std::ranges::find(biome_enums, biome));
-}
-
-chunk::block_array
-chunk::make_blocks_from_chunk(const proto::chunk& chunk) noexcept {
- chunk::block_array blocks;
-
- for (int i = 0; i < chunk.blocks_size(); ++i) {
- const std::uint32_t packed_blocks = chunk.blocks(i);
- for (int j = 0; j < 4; ++j) {
- blocks[static_cast<unsigned>(i * 4 + j)] =
- static_cast<enum block::type>(
- static_cast<std::uint8_t>(packed_blocks >> j * 8));
- }
- }
-
- return blocks;
-}
-
-// Returns a deterministic random number based on the args.
-static unsigned long make_prandom(const uint64_t seed,
- const shared::math::coords& coords) noexcept {
- const auto ulx = static_cast<unsigned long>(coords.x);
- const auto ulz = static_cast<unsigned long>(coords.z);
- return std::ranlux48{std::ranlux48{std::ranlux48{seed}() + ulx}() + ulz}();
-}
-
-// Returns a pseduorandom gradient vector.
-static glm::vec2 make_gvect(const std::uint64_t& seed,
- const shared::math::coords& coords) noexcept {
- const unsigned long pseudo = make_prandom(seed, coords);
- // Return a vector based on the first four bits of this random number.
- // This vector can point in dirs (x with a + on it) with range [-1, 1].
- const float v1 = ((pseudo & 0b00001111) / 7.5f) - 1.0f;
- const float v2 = (((pseudo & 0b11110000) >> 4) / 7.5f) - 1.0f;
- return {v1, v2};
-}
-
-// Returns a distance vector between a vector and a grid position in a chunk.
-static glm::vec2 make_dvect(const glm::vec2 v, const unsigned int x,
- const unsigned int z, const int width) noexcept {
- const auto div = static_cast<float>(width - 1);
- const float v1 = v.x - (static_cast<float>(x) / div);
- const float v2 = v.y - (static_cast<float>(z) / div);
- return {v1, v2};
-}
-
-static float fade(const float v) noexcept {
- return v * v * v * (v * (v * 6 - 15) + 10);
-}
-
-static std::int32_t reflect_outer(const std::int32_t& n,
- const int WIDTH = chunk::WIDTH) noexcept {
- return n < 0 ? ((n - (WIDTH - 1)) / WIDTH) : n / WIDTH;
-}
-
-static std::int32_t reflect_inner(const std::int32_t& n,
- const int WIDTH = chunk::WIDTH) noexcept {
- return ((n % WIDTH) + WIDTH) % WIDTH;
-}
-
-// Moves perlin noise values from the range of [-1.0f, 1.0f] to [0.0f, 1.0f].
-// It's more useful to have a perlin layer add nothing at its absolute lowest,
-// then it is for a perlin layer to potentially remove 1.0f at its lowest.
-static float normalise_perlin(const float perlin) noexcept {
- return 0.5f * (perlin + 1.0f);
-}
-
-using chunk_array = std::array<std::array<float, chunk::WIDTH>, chunk::WIDTH>;
-using chunk_array_map =
- std::unordered_map<shared::math::coords, chunk_array,
- decltype(&chunk::hash), decltype(&chunk::equal)>;
-
-// 2D Perlin noise, in which we:
-// 1. Define corners of a square as vectors, in this case using 0 or 1.
-// 2. Assign pseudorandom normalised gradient vectors to each corner vector.
-// 3. Create and iterate through a 2d array, where we:
-// 3.1: Generate distance vectors from each corner vector to the cell.
-// 3.2: Dot our gradient vectors and distance vectors respectively.
-// 3.3: Lerp our bottom and top dot product values by a fade function and x.
-// 3.4: Lerp (3.3) via fade function and z. This is the cell result.
-// There is an additional step where we make use of "scale" (any s* var). This
-// involves moving the requested chunk into a potentially different chunk, and
-// accessing a greater level of detail. It's just zoom and enhance.
-static chunk_array make_2d_perlin_array(const std::uint64_t& seed,
- const shared::math::coords& pos,
- const int scale) noexcept {
- constexpr glm::vec2 tr = {1.0f, 1.0f}; // (1)
- constexpr glm::vec2 tl = {0.0f, 1.0f};
- constexpr glm::vec2 bl = {0.0f, 0.0f};
- constexpr glm::vec2 br = {1.0f, 0.0f};
-
- const int scx = reflect_outer(pos.x, scale);
- const int scz = reflect_outer(pos.z, scale);
- const int swidth = chunk::WIDTH * scale;
-
- // clang-format off
- const glm::vec2 tr_g = glm::normalize(make_gvect(seed, shared::math::coords{scx, scz})); // (2)
- const glm::vec2 tl_g = glm::normalize(make_gvect(seed, shared::math::coords{scx - 1, scz}));
- const glm::vec2 bl_g = glm::normalize(make_gvect(seed, shared::math::coords{scx - 1, scz - 1}));
- const glm::vec2 br_g = glm::normalize(make_gvect(seed, shared::math::coords{scx, scz - 1}));
- // clang-format on
-
- chunk_array perlin; // (3)
-
- const int x_offset = reflect_inner(pos.x, scale) * chunk::WIDTH;
- const int z_offset = reflect_inner(pos.z, scale) * chunk::WIDTH;
- for (auto x = 0u; x < chunk::WIDTH; ++x) {
- const unsigned sx = x + static_cast<unsigned>(x_offset);
-
- for (auto z = 0u; z < chunk::WIDTH; ++z) {
- const unsigned sz = z + static_cast<unsigned>(z_offset);
-
- const glm::vec2 tr_d = make_dvect(tr, sx, sz, swidth); // (3.1)
- const glm::vec2 tl_d = make_dvect(tl, sx, sz, swidth);
- const glm::vec2 bl_d = make_dvect(bl, sx, sz, swidth);
- const glm::vec2 br_d = make_dvect(br, sx, sz, swidth);
-
- const float tr_dp = glm::dot(tr_g, tr_d); // (3.2)
- const float tl_dp = glm::dot(tl_g, tl_d);
- const float bl_dp = glm::dot(bl_g, bl_d);
- const float br_dp = glm::dot(br_g, br_d);
-
- const float fswidth = static_cast<float>(swidth - 1);
- const float fracx = (static_cast<float>(sx) + 0.5f) / fswidth;
- const float fracz = (static_cast<float>(sz) + 0.5f) / fswidth;
- const float tl_tr = std::lerp(tl_dp, tr_dp, fade(fracx)); // (3.3)
- const float bl_br = std::lerp(bl_dp, br_dp, fade(fracx));
-
- const float result = std::lerp(tl_tr, bl_br, fade(1.0f - fracz));
-
- perlin[x][z] = normalise_perlin(result); // (3.4)
- }
- }
-
- return perlin;
-}
-
-using biome_array = std::array<
- std::array<std::array<float, std::size(biome_enums)>, chunk::WIDTH>,
- chunk::WIDTH>;
-using biome_array_map =
- std::unordered_map<shared::math::coords, biome_array,
- decltype(&chunk::hash), decltype(&chunk::equal)>;
-// A 2d_biome_array is a 2d array containing a [0.0f-1.0f] value, tied to a
-// biome, representing the weight the biomes have on each column in the world.
-// Naturally each column should take that % of each biome's ruleset during
-// worldgen, resuling in an amount of rules that adds up to 100%.
-
-// Unfortunately this is a bit of a hell function because it is full of very
-// specific operations that can't be reasonably separated without decreasing
-// random number generation complexity or exposing a ton of random static
-// functions that will never be used outside of this.
-// Basically I am trying to minimise this bad code.
-static biome_array make_2d_biome_array(const std::uint64_t& seed,
- const shared::math::coords& coords,
- const int scale) noexcept {
-
- // Decreasing NUM_POINTS to the smallest meaningful value of 1 while
- // simultaneously decreasing our scale results in FAR lower computational
- // cost while achieving approximately the same result. This is because we
- // only have to generate 9 random numbers, as opposed to n * 9, which is a
- // performance KILLER.
- constexpr int NUM_POINTS = 1;
-
- struct point {
- glm::vec2 pos;
- enum chunk::biome biome;
- };
- using point_array = std::array<struct point, NUM_POINTS>;
- const auto make_points =
- [&seed](const shared::math::coords& c) -> point_array {
- constexpr int BITS_PER_FLOAT = 48 / 2; // ranlux 48 / 2
- constexpr int MASK = ((2 << BITS_PER_FLOAT) - 1);
- static_assert(BITS_PER_FLOAT * 2 * NUM_POINTS <= 48);
-
- const unsigned long prand = make_prandom(seed, c);
- std::uniform_int_distribution<> uniform{0, std::size(biome_enums) - 1};
- std::ranlux48 generator{prand};
-
- point_array points;
- std::ranges::generate(points, [&, n = 0]() mutable -> point {
- const int x = (prand >> ((n + 1) * BITS_PER_FLOAT)) & MASK;
- const int y = (prand >> ((n)*BITS_PER_FLOAT)) & MASK;
- const glm::vec2 pos = glm::vec2{x, y} / static_cast<float>(MASK);
- const auto biome = static_cast<chunk::biome>(uniform(generator));
- ++n;
- return {pos, biome};
- });
- return points;
- };
-
- const shared::math::coords scaled_coords{reflect_outer(coords.x, scale),
- reflect_outer(coords.z, scale)};
-
- using point_2d_array = std::array<std::array<point_array, 3>, 3>;
- const point_2d_array point_arrays = [&]() -> point_2d_array {
- point_2d_array point_arrays;
- for (int x = -1; x <= 1; ++x) {
- for (int z = -1; z <= 1; ++z) {
- const auto offset_coords =
- scaled_coords + shared::math::coords{x, z};
- const auto index_x = static_cast<unsigned long>(x + 1);
- const auto index_z = static_cast<unsigned long>(z + 1);
- point_arrays[index_x][index_z] = make_points(offset_coords);
- }
- }
- return point_arrays;
- }();
-
- struct point_info {
- struct point point;
- glm::vec2 pos;
- shared::math::coords coords;
- float distance;
- };
- using point_info_vector = std::vector<point_info>;
- const auto get_closest_point_infos =
- [&](const glm::vec2 pos) -> point_info_vector {
- std::vector<point_info> point_infos;
- for (int x = -1; x <= 1; ++x) {
- for (int z = -1; z <= 1; ++z) {
- const glm::vec2 offset = {x, z};
- const point_array& point_array =
- point_arrays[static_cast<unsigned long>(x + 1)]
- [static_cast<unsigned long>(z + 1)];
-
- for (const auto& point : point_array) {
-
- const float distance =
- glm::distance(point.pos + offset, pos);
- if (distance > 1.0f) {
- continue;
- }
-
- point_infos.push_back(point_info{
- .point = point,
- .pos = point.pos,
- .coords = shared::math::coords{x, z} + scaled_coords,
- .distance = distance});
- }
- }
- }
- return point_infos;
- };
-
- const int x_offset = reflect_inner(coords.x, scale) * chunk::WIDTH;
- const int z_offset = reflect_inner(coords.z, scale) * chunk::WIDTH;
- const int scaled_width = chunk::WIDTH * scale;
-
- // We generate a bit of perlin noise here so we can add some (jitter?)
- // along our distances - the end result is less of an obvious line and more
- // variance along our chunk borders.
- constexpr int BIOME_JITTER_SCALE = 3;
- const chunk_array jitter =
- make_2d_perlin_array(seed, coords, BIOME_JITTER_SCALE);
-
- // For our 2d array (ultimately columns of blocks in the world), we get the
- // point that we're closest to in our world's voronoi noise points. For
- // the points that are relevant (relatively close), we get the biome the
- // point represents and add it to the column's array of biome influences.
- // We ensure that these values add up to 1.0f. In the end, we have a struct
- // that describes how much each biome affects each column as a %.
- biome_array array = {};
- for (auto x = 0u; x < chunk::WIDTH; ++x) {
- const unsigned sx = x + static_cast<unsigned>(x_offset);
-
- for (auto z = 0u; z < chunk::WIDTH; ++z) {
- const unsigned sz = z + static_cast<unsigned>(z_offset);
-
- const glm::vec2 inner_pos =
- glm::vec2{static_cast<float>(sx) + 0.5f,
- static_cast<float>(sz) + 0.5f} /
- static_cast<float>(scaled_width) +
- glm::vec2{std::sin(jitter[x][z]), std::cos(jitter[x][z])} *
- 0.1f;
-
- const auto point_infos = get_closest_point_infos(inner_pos);
-
- float total_dominance = 0.0f;
- for (const auto& point_info : point_infos) {
- const auto index = get_biome_index(point_info.point.biome);
- const float dominance = std::clamp(
- -1.0f * std::pow(0.5f * point_info.distance - 1.0f, 21.0f),
- 0.0f, 1.0f);
-
- auto& loc = array[x][z][static_cast<unsigned long>(index)];
- if (loc > dominance) {
- continue;
- }
- const float diff = dominance - loc;
- loc += diff;
- total_dominance += diff;
- }
-
- for (float& dominance : array[x][z]) {
- dominance *= (1.0f / total_dominance);
- }
- }
- }
- return array;
-}
-
-static auto array_map_access(const auto& array_map,
- const shared::math::coords& coords, const int x,
- const int z) noexcept {
- const shared::math::coords normalised_coords =
- chunk::get_normalised_chunk(coords, x, z);
- const auto [nx, nz] = chunk::get_normalised_coords(x, z);
- return array_map.find(normalised_coords)->second[nx][nz];
-}
-
-// We take a std::function that generates a chunk array to fill an unordered
-// map with a 3x3 chunk_array contents, where those contents refer to the
-// values in the surrounding chunks.
-static auto make_array_map(const std::uint64_t& seed,
- const shared::math::coords& coords,
- const auto& make_chunk_array_func) noexcept {
-
- std::unordered_map<shared::math::coords,
- decltype(make_chunk_array_func(seed, coords)),
- decltype(&chunk::hash), decltype(&chunk::equal)>
- array_map{9, chunk::hash, chunk::equal};
- for (int x = -1; x <= 1; ++x) {
- for (int z = -1; z <= 1; ++z) {
- const shared::math::coords pos{coords + shared::math::coords{x, z}};
- array_map.emplace(pos, make_chunk_array_func(seed, pos));
- }
- }
-
- return array_map;
-}
-
-// These are constexpr for our static assert.
-static constexpr float
-get_biome_offset(const enum chunk::biome biome) noexcept {
- switch (biome) {
- case chunk::biome::ocean:
- case chunk::biome::islands:
- return 0.0f;
- default:
- break;
- }
- return 10.0f;
-}
-
-static constexpr float
-get_biome_variation17(const enum chunk::biome biome) noexcept {
- switch (biome) {
- case chunk::biome::alpine:
- return 80.0f;
- case chunk::biome::tundra:
- return 10.0f;
- case chunk::biome::forest:
- return 30.0f;
- case chunk::biome::ocean:
- return 0.0f;
- case chunk::biome::islands:
- return 5.0f;
- default:
- break;
- }
- return 15.0f;
-}
-
-static constexpr float
-get_biome_variation11(const enum chunk::biome biome) noexcept {
- switch (biome) {
- break;
- case chunk::biome::alpine:
- return 40.0f;
- case chunk::biome::tundra:
- return 30.0f;
- default:
- break;
- }
- return 20.0f;
-}
-
-static constexpr float
-get_biome_variation7(const enum chunk::biome biome) noexcept {
- switch (biome) {
- case chunk::biome::alpine:
- return 20.0f;
- case chunk::biome::islands:
- return 30.0f;
- case chunk::biome::desert:
- case chunk::biome::plains:
- return 15.0f;
- default:
- break;
- }
- return 10.0f;
-}
-
-static constexpr float
-get_biome_variation3(const enum chunk::biome biome) noexcept {
- switch (biome) {
- default:
- break;
- }
- return 7.5f;
-}
-
-constexpr float BASE_HEIGHT = 40.0f;
-// Ensure any perlin values of our biome generation does not result in a y value
-// that is outside our max height.
-static_assert(std::ranges::all_of(biome_enums, [](const auto& biome) {
- const float max_height =
- BASE_HEIGHT + get_biome_offset(biome) + get_biome_variation3(biome) +
- get_biome_variation7(biome) + get_biome_variation11(biome) +
- get_biome_variation17(biome);
- return max_height < static_cast<float>(chunk::HEIGHT);
-}));
-
-// Line that crosses at 0.5f, 0.5f with a variable gradient, should be clamped.
-static float linear_gradient(const float x, const float m) noexcept {
- return m * (x - (0.5f - (0.5f / m)));
-}
-
-// The functions for ...these functions... should be domain and range [0, 1].
-static float process_variation(float variation,
- const enum chunk::biome biome) noexcept {
- switch (biome) {
- case chunk::biome::alpine:
- variation = linear_gradient(variation, 2.2f);
- break;
- default:
- variation = linear_gradient(variation, 1.5f);
- break;
- }
- return std::clamp(variation, 0.0f, 1.0f);
-}
-
-chunk_array make_topography(const std::uint64_t& seed,
- const shared::math::coords& coords,
- const biome_array_map& biome_map) noexcept {
-
- chunk_array topography = {};
-
- const biome_array& biomes = biome_map.find(coords)->second;
- const chunk_array perlin17 = make_2d_perlin_array(seed, coords, 17);
- const chunk_array perlin11 = make_2d_perlin_array(seed, coords, 11);
- const chunk_array perlin7 = make_2d_perlin_array(seed, coords, 7);
- const chunk_array perlin3 = make_2d_perlin_array(seed, coords, 3);
-
- for (auto x = 0ul; x < chunk::WIDTH; ++x) {
- for (auto z = 0ul; z < chunk::WIDTH; ++z) {
-
- // Initial topography of 40.0f.
- topography[x][z] = BASE_HEIGHT;
-
- const auto biome_dominance = biomes[x][z];
- for (auto i = 0u; i < std::size(biome_dominance); ++i) {
- const enum chunk::biome biome = biome_enums[i];
-
- const float dominance = biome_dominance[i];
- const float v3 = process_variation(perlin3[x][z], biome) *
- get_biome_variation3(biome);
- const float v7 = process_variation(perlin7[x][z], biome) *
- get_biome_variation7(biome);
- const float v11 = process_variation(perlin11[x][z], biome) *
- get_biome_variation11(biome);
- const float v17 = process_variation(perlin17[x][z], biome) *
- get_biome_variation17(biome);
-
- topography[x][z] +=
- (get_biome_offset(biome) + v3 + v7 + v11 + v17) * dominance;
- }
- }
- }
-
- return topography;
-}
-
-static chunk_array
-make_probabilities(const std::uint64_t& seed,
- const shared::math::coords& coords) noexcept {
-
- chunk_array chunk_array;
-
- std::uniform_real_distribution<float> uniform{
- 0.0f, std::nextafter(1.0f, std::numeric_limits<float>::max())};
- std::ranlux48 generator(make_prandom(seed, coords));
- for (auto x = 0ul; x < chunk::WIDTH; ++x) {
- for (auto z = 0ul; z < chunk::WIDTH; ++z) {
- chunk_array[x][z] = uniform(generator);
- }
- }
-
- return chunk_array;
-}
-
-static biome_array make_biomes(const std::uint64_t& seed,
- const shared::math::coords& coords) noexcept {
-
- constexpr int BIOME_SCALE = 99;
- return make_2d_biome_array(seed, coords, BIOME_SCALE);
-}
-
-constexpr int SAND_HEIGHT = 65;
-constexpr int WATER_HEIGHT = 63;
-
-constexpr int SAND_DEPTH = 5;
-constexpr int SANDSTONE_DEPTH = 6;
-constexpr int GRASS_DEPTH = 1;
-constexpr int DIRT_DEPTH = 7;
-
-// Rulesets common to all chunks go here. Water inhabits all blocks below 63
-// and above the topography value. In addition, stone fills all blocks after
-// the initial biome blocks.
-static void generate_post_terrain_column(chunk::block_array& blocks,
- const glm::ivec3& pos,
- const int lowest) noexcept {
- for (int y = lowest; y > WATER_HEIGHT; --y) {
- blocks[chunk::get_3d_index({pos.x, y, pos.z})] = block::type::sand;
- }
- for (int y = WATER_HEIGHT; y > pos.y; --y) {
- blocks[chunk::get_3d_index({pos.x, y, pos.z})] = block::type::water;
- }
- for (int y = lowest; y >= 0; --y) {
- blocks[chunk::get_3d_index({pos.x, y, pos.z})] = block::type::stone;
- }
-}
-
-static int generate_sandy_terrain_column(chunk::block_array& blocks,
- const glm::ivec3& pos) noexcept {
- int y = pos.y;
- for (const int lowest = y - SAND_DEPTH; y > lowest; --y) {
- blocks[chunk::get_3d_index({pos.x, y, pos.z})] = block::type::sand;
- }
- for (const int lowest = y - SANDSTONE_DEPTH; y > lowest; --y) {
- blocks[chunk::get_3d_index({pos.x, y, pos.z})] = block::type::sandstone;
- }
- return y;
-}
-
-static int generate_grassy_terrain_column(chunk::block_array& blocks,
- const glm::ivec3& pos,
- const enum block::type top) noexcept {
- int y = pos.y;
- if (y <= SAND_HEIGHT) {
- for (const int lowest = y - SAND_DEPTH; y > lowest; --y) {
- blocks[chunk::get_3d_index({pos.x, y, pos.z})] = block::type::sand;
- }
- } else {
- for (const int lowest = y - GRASS_DEPTH; y > lowest; --y) {
- blocks[chunk::get_3d_index({pos.x, y, pos.z})] = top;
- }
- for (const int lowest = y - DIRT_DEPTH; y > lowest; --y) {
- blocks[chunk::get_3d_index({pos.x, y, pos.z})] = block::type::dirt;
- }
- }
- return y;
-}
-
-static void generate_terrain_column(chunk::block_array& blocks,
- const glm::ivec3& pos,
- const enum chunk::biome biome) noexcept {
- const int lowest = [&]() {
- switch (biome) {
- case chunk::biome::desert:
- return generate_sandy_terrain_column(blocks, pos);
-
- case chunk::biome::islands:
- case chunk::biome::forest:
- case chunk::biome::plains:
- case chunk::biome::ocean:
- return generate_grassy_terrain_column(blocks, pos,
- block::type::grass);
-
- case chunk::biome::tundra:
- case chunk::biome::alpine:
- return generate_grassy_terrain_column(blocks, pos,
- block::type::snow);
- }
- }();
-
- generate_post_terrain_column(blocks, pos, lowest);
-}
-
-static enum chunk::biome get_dominant_biome(const auto& array) noexcept {
- const auto max_it =
- std::max_element(std::begin(array), std::end(array),
- [](const auto& a, const auto& b) { return a < b; });
- return biome_enums[static_cast<unsigned long>(
- std::distance(std::begin(array), max_it))];
-}
-
-static void generate_terrain(chunk::block_array& blocks,
- const shared::math::coords& coords,
- const chunk_array_map& topography_map,
- const biome_array_map& biome_map) noexcept {
-
- const biome_array& biomes = biome_map.find(coords)->second;
- const chunk_array& topography = topography_map.find(coords)->second;
-
- // We fill in our chunk column by column, where the column refers to a
- // function which maps to a biome.
- for (unsigned long x = 0ul; x < chunk::WIDTH; ++x) {
- for (unsigned long z = 0ul; z < chunk::WIDTH; ++z) {
- generate_terrain_column(blocks, glm::vec3{x, topography[x][z], z},
- get_dominant_biome(biomes[x][z]));
- }
- }
-}
-
-// Objects have a max WIDTH, but no max WIDTH.
-constexpr int MAX_OBJECT_WIDTH = 5;
-constexpr int OBJECT_HALFWIDTH = MAX_OBJECT_WIDTH / 2;
-// A layer is an x, z slice of an object.
-using object_layer =
- std::array<std::array<block, MAX_OBJECT_WIDTH>, MAX_OBJECT_WIDTH>;
-using object = std::vector<object_layer>;
-struct biome_object {
- const object* const blocks;
- const float probability;
- const int altitude; // lowest alt we can gen at
-};
-using biome_objects = std::vector<biome_object>;
-
-static object make_tree_object() noexcept {
- object tree;
- object_layer layer = {};
-
- layer[OBJECT_HALFWIDTH][OBJECT_HALFWIDTH] = block::type::wood;
- for (int i = 0; i < 3; ++i) {
- tree.push_back(layer);
- }
-
- for (unsigned x = 0; x < std::size(layer); ++x) {
- for (unsigned z = 0; z < std::size(layer); ++z) {
- if (x == OBJECT_HALFWIDTH && z == OBJECT_HALFWIDTH) {
- continue;
- }
- layer[x][z] = block::type::leaves;
- }
- }
- for (int i = 0; i < 2; ++i) {
- tree.push_back(layer);
- }
- for (unsigned x = 0; x < std::size(layer); ++x) {
- for (unsigned z = 0; z < std::size(layer); ++z) {
- if (!(x == 0 || x == 4 || z == 0 || z == 4)) {
- continue;
- }
- layer[x][z] = block::type::air;
- }
- }
- tree.push_back(layer);
- for (unsigned x = 0; x < std::size(layer); ++x) {
- for (unsigned z = 0; z < std::size(layer); ++z) {
- if (x < 1 || x > 3 || z < 1 || z > 3 ||
- (std::abs(int(x) - 2) + std::abs(int(z) - 2) == 2)) {
- layer[x][z] = block::type::air;
- continue;
- }
- layer[x][z] = block::type::leaves;
- }
- }
- tree.push_back(layer);
- return tree;
-}
-
-static object make_alpine_tree() noexcept {
- object tree;
- object_layer layer = {};
-
- layer[OBJECT_HALFWIDTH][OBJECT_HALFWIDTH] = block::type::snowy_wood;
- constexpr int HEIGHT = 10;
- for (int y = 0; y <= HEIGHT; ++y) {
-
- constexpr float BASE_RAD = static_cast<float>(MAX_OBJECT_WIDTH) / 2.0f;
-
- const float radius =
- BASE_RAD * std::exp(-1.0f * (static_cast<float>(y) /
- static_cast<float>(HEIGHT)));
-
- for (unsigned x = 0; x < MAX_OBJECT_WIDTH; ++x) {
- for (unsigned z = 0; z < MAX_OBJECT_WIDTH; ++z) {
- if (x == OBJECT_HALFWIDTH && z == OBJECT_HALFWIDTH) {
- if (y == HEIGHT) {
- layer[x][z] = block::type::snowy_leaves;
- }
-
- continue; // leave as wood
- }
-
- if ((y + 1) % 2) {
- layer[x][z] = block::type::air;
- continue;
- }
-
- const float lhs = std::pow((float)x - 2.0f, 2.0f) +
- std::pow((float)z - 2.0f, 2.0f);
- const float rhs = std::pow(radius, 2.0f);
-
- layer[x][z] =
- lhs < rhs ? block::type::snowy_leaves : block::type::air;
- }
- }
-
- tree.push_back(layer);
- }
-
- return tree;
-}
-
-static object make_column_object(const enum block::type type,
- const int WIDTH) noexcept {
- object obj;
- object_layer layer = {};
-
- layer[OBJECT_HALFWIDTH][OBJECT_HALFWIDTH] = type;
- for (int i = 0; i < WIDTH; ++i) {
- obj.push_back(layer);
- }
- return obj;
-}
-
-static const biome_objects&
-get_biome_objects(const chunk::biome biome) noexcept {
- using btype = enum block::type;
- static const object tree = make_tree_object();
- static const object alpine_tree = make_alpine_tree();
- static const object cactus1 = make_column_object(btype::cactus, 1);
- static const object cactus2 = make_column_object(btype::cactus, 2);
- static const object cactus3 = make_column_object(btype::cactus, 3);
- static const object grass = make_column_object(btype::shrub, 1);
- static const object dead_shrub = make_column_object(btype::dead_shrub, 1);
- static const object snowy_grass = make_column_object(btype::snowy_shrub, 1);
-
- switch (biome) {
- case chunk::biome::islands:
- static const biome_objects island_objects{
- biome_object{&tree, 0.0005f, SAND_HEIGHT},
- biome_object{&grass, 0.025f, SAND_HEIGHT}};
- return island_objects;
-
- case chunk::biome::plains:
- static const biome_objects plains_objects{
- biome_object{&tree, 0.0005f, SAND_HEIGHT},
- biome_object{&grass, 0.1f, SAND_HEIGHT}};
- return plains_objects;
-
- case chunk::biome::forest:
- static const biome_objects forest_objects{
- biome_object{&tree, 0.01f, SAND_HEIGHT},
- biome_object{&grass, 0.1f, SAND_HEIGHT}};
- return forest_objects;
-
- case chunk::biome::desert:
- static const biome_objects desert_objects{
- biome_object{&cactus1, 0.0002f, WATER_HEIGHT},
- biome_object{&cactus2, 0.0005f, WATER_HEIGHT},
- biome_object{&cactus3, 0.0009f, WATER_HEIGHT},
- biome_object{&dead_shrub, 0.0012f, WATER_HEIGHT}};
- return desert_objects;
-
- case chunk::biome::alpine:
- static const biome_objects alpine_objects{
- biome_object{&alpine_tree, 0.0008f, SAND_HEIGHT},
- biome_object{&snowy_grass, 0.0025f, SAND_HEIGHT}};
- return alpine_objects;
-
- case chunk::biome::tundra:
- static const biome_objects tundra_objects{
- biome_object{&alpine_tree, 0.0008f, SAND_HEIGHT},
- biome_object{&snowy_grass, 0.004f, SAND_HEIGHT}};
- return tundra_objects;
-
- case chunk::biome::ocean:
- static const biome_objects ocean_objects{
- biome_object{&tree, 0.001f, SAND_HEIGHT},
- biome_object{&grass, 0.1f, SAND_HEIGHT}};
- return ocean_objects;
- }
-}
-
-static std::optional<biome_object>
-maybe_get_object(const float probability, const chunk::biome biome) noexcept {
- const auto& biome_objects = get_biome_objects(biome);
-
- const auto find_it =
- std::ranges::find_if(biome_objects, [&](const auto& object) {
- return object.probability >= probability;
- });
-
- if (find_it != std::end(biome_objects)) {
- return *find_it;
- }
- return std::nullopt;
-}
-
-static int get_block_priority(const enum block::type& b) noexcept {
- switch (b) {
- case block::type::air:
- return 0;
- case block::type::leaves:
- case block::type::snowy_leaves:
- return 1;
- default:
- break;
- }
- return 2;
-}
-
-static void generate_object(const object& object, chunk::block_array& blocks,
- const glm::ivec3& pos) noexcept {
- for (unsigned y = 0; y < std::size(object); ++y) {
-
- const auto& layer = object[y];
- for (unsigned x = 0; x < std::size(layer); ++x) {
- for (unsigned z = 0; z < std::size(layer); ++z) {
-
- const block block = layer[x][z];
- if (block == block::type::air) {
- continue;
- }
-
- const glm::ivec3 block_pos =
- pos + glm::ivec3{x - MAX_OBJECT_WIDTH / 2, y + 1,
- z - MAX_OBJECT_WIDTH / 2};
- if (chunk::is_outside_chunk(block_pos)) {
- continue;
- }
-
- const auto index = chunk::get_3d_index(block_pos);
-
- const class block old_block = blocks[index];
- if (get_block_priority(block) < get_block_priority(old_block)) {
- continue;
- }
-
- blocks[index] = block;
- }
- }
- }
-}
-
-static void generate_objects(chunk::block_array& blocks,
- const shared::math::coords& coords,
- const chunk_array_map& topography_map,
- const biome_array_map& biome_map,
- const chunk_array_map& probability_map) noexcept {
-
- // This is really expensive as it is x^2
- // We don't want to create any structures that are very wide as a result.
- // (at least, structures which generate via this method).
- const int MAX_READ = MAX_OBJECT_WIDTH / 2;
- for (int x = -MAX_READ; x < MAX_READ + chunk::WIDTH; ++x) {
- for (int z = -MAX_READ; z < MAX_READ + chunk::WIDTH; ++z) {
- const float probability =
- array_map_access(probability_map, coords, x, z);
- const chunk::biome biome =
- get_dominant_biome(array_map_access(biome_map, coords, x, z));
-
- const auto& object = maybe_get_object(probability, biome);
- if (!object.has_value()) {
- continue;
- }
-
- const glm::ivec3 pos{
- x, array_map_access(topography_map, coords, x, z), z};
-
- if (pos.y <= object->altitude) {
- continue;
- }
-
- generate_object(*object->blocks, blocks, pos);
- }
- }
-}
-
-static chunk::block_array
-generate_blocks(const std::uint64_t& seed,
- const shared::math::coords& coords) noexcept {
-
- const biome_array_map biome_arrays =
- make_array_map(seed, coords, make_biomes);
- // Topography relies on block temperature so we bind it as a hidden
- // third argument as to avoid unnecessary generation.
- const chunk_array_map topography_arrays =
- make_array_map(seed, coords,
- std::bind(make_topography, std::placeholders::_1,
- std::placeholders::_2, biome_arrays));
-
- const chunk_array_map probability_map =
- make_array_map(seed, coords, make_probabilities);
-
- chunk::block_array blocks = {};
-
- // Must be called in order, generate_objects relies on generate terrain etc.
- generate_terrain(blocks, coords, topography_arrays, biome_arrays);
- generate_objects(blocks, coords, topography_arrays, biome_arrays,
- probability_map);
- // generate caves
- // generate better terrain generation
- return blocks;
-}
-
-// Use perlin noise and extrapolation of eigen vectors along a mobius strip.
-chunk::chunk(const std::uint64_t& seed, const shared::math::coords& coords,
- std::optional<decltype(chunk::blocks)> blocks) noexcept
- : pos(coords) {
- if (blocks.has_value()) {
- this->blocks = std::move(blocks.value());
- return;
- }
-
- this->blocks = generate_blocks(seed, coords);
-}
-
-} // namespace world
-} // namespace shared