diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 21:57:46 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-12 21:57:46 +1100 |
| commit | e4483eca01b48b943cd0461e24a74ae1a3139ed4 (patch) | |
| tree | ed58c3c246e3af1af337697695d780aa31f6ad9a /src/shared/world.cc | |
| parent | 1cc08c51eb4b0f95c30c0a98ad1fc5ad3459b2df (diff) | |
Update to most recent version (old initial commit)
Diffstat (limited to 'src/shared/world.cc')
| -rw-r--r-- | src/shared/world.cc | 932 |
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 |
