diff --git a/src/game/load.cpp b/src/game/load.cpp index 5ee7dc4..4a1c916 100644 --- a/src/game/load.cpp +++ b/src/game/load.cpp @@ -49,7 +49,7 @@ void biome(game::context& ctx, const std::filesystem::path& path) image img; img.format(1, 1); - img.resize(2048, 2048); + img.resize(1024, 1024); float frequency = 10.0f; std::size_t octaves = 4; @@ -108,15 +108,17 @@ void biome(game::context& ctx, const std::filesystem::path& path) f1_sqr_distance, f1_displacement, f1_id, - f2_sqr_distance, - f2_displacement, - f2_id - ] = math::noise::voronoi::f1_f2(position, 1.0f, {0.0f, 0.0f}, &math::noise::hash::pcg3d_3); + // f2_sqr_distance, + // f2_displacement, + // f2_id + edge_sqr_distance + ] = math::noise::voronoi::f1_edge({position[0], position[1], 0.5f}, 1.0f, {0.0f, 0.0f, 0.0f}, &math::noise::hash::pcg3d_3); float f1_distance = std::sqrt(f1_sqr_distance); - float f2_distance = std::sqrt(f2_sqr_distance); + //float f2_distance = std::sqrt(f2_sqr_distance); + float edge_distance = std::sqrt(edge_sqr_distance); - pixel = static_cast(std::min(255.0f, f1_distance * 255.0f)); + pixel = static_cast(std::min(255.0f, edge_distance * 255.0f)); //pixel = static_cast(id % 255); } ); diff --git a/src/math/noise/voronoi.hpp b/src/math/noise/voronoi.hpp index d8465dd..1f5d739 100644 --- a/src/math/noise/voronoi.hpp +++ b/src/math/noise/voronoi.hpp @@ -22,9 +22,11 @@ #include "math/vector.hpp" #include +#include #include #include #include +#include namespace math { namespace noise { @@ -33,38 +35,67 @@ namespace noise { namespace voronoi { /** - * Number of neighboring cells that must be searched for F1. + * Number of Voronoi cells to search. + * + * @tparam N Number of dimensions. + * + * @private + */ +template +constexpr std::size_t kernel_size = 4 << std::max(0, (2 * (N - 1))); + +/** + * Generates an kernel offset vector for a given index. + * + * @tparam T Real type. + * @tparam N Number of dimensions. + * @tparam I Index sequence. + * + * @param i Index of a kernel offset vector. + * + * @return Kernel offset vector. * * @private */ -constexpr std::size_t f1_kernel_size = 12; +template +constexpr vector kernel_offset(std::size_t i, std::index_sequence) +{ + return {static_cast((I ? (i / (2 << std::max(0, 2 * I - 1))) : i) % 4)...}; +} /** - * Offsets to neighboring cells that must be searched for F1. + * Generates a Voronoi search kernel. + * + * @tparam T Real type. + * @tparam N Number of dimensions. + * @tparam I Index sequence. + * + * @return Voronoi search kernel. * * @private */ -template -constexpr vector f1_kernel[f1_kernel_size] = +template +constexpr std::array, kernel_size> generate_kernel(std::index_sequence) { - /*****/ {1, 0}, {2, 0}, /*****/ - {0, 1}, {1, 1}, {2, 1}, {3, 1}, - {0, 2}, {1, 2}, {2, 2}, {3, 2}, - /*****/ {1, 3}, {2, 3} /*****/ -}; + return {kernel_offset(I, std::make_index_sequence{})...}; +} /** - * Maximum squared distance to the nearest F1 cell center. + * *n*-dimensional search kernel. + * + * @tparam T Real type. + * @tparam N Number of dimensions. * * @private */ -template -constexpr T f1_max_sqr_distance = T{8}; +template +constexpr auto kernel = generate_kernel(std::make_index_sequence>{}); /** * Finds the Voronoi cell (F1) containing the input position. * * @tparam T Real type. + * @tparam N Number of dimensions. * @tparam U Hash function return type. * * @param position Input position. @@ -74,58 +105,59 @@ constexpr T f1_max_sqr_distance = T{8}; * * @return Tuple containing the square Euclidean distance from @p position to the F1 cell, the displacement vector from the input position to the F1 cell center, and a hash value indicating the ID of the F1 cell. */ -template +template std::tuple < // F1 square distance to center T, // F1 position to center displacement - vector, + vector, // F1 hash U > f1 ( - const vector& position, + const vector& position, T randomness, - const vector& tiling, - vector (*hash)(const vector&) + const vector& tiling, + vector (*hash)(const vector&) ) { // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness T hash_scale = (T{1} / static_cast(std::numeric_limits::max())) * randomness; // Get integer and fractional parts - vector position_i = math::floor(position - T{1.5}); - vector position_f = position - position_i; + vector position_i = math::floor(position - T{1.5}); + vector position_f = position - position_i; // Find the F1 cell - T f1_sqr_distance = f1_max_sqr_distance; - vector f1_displacement; + T f1_sqr_distance = std::numeric_limits::infinity(); + vector f1_displacement; U f1_hash; - for (std::size_t i = 0; i < f1_kernel_size; ++i) + for (std::size_t i = 0; i < kernel_size; ++i) { // Get kernel offset for current cell - const vector& offset_i = f1_kernel[i]; + const vector& offset_i = kernel[i]; // Calculate hash input position, tiling where specified - vector hash_position = position_i + offset_i; - if (tiling[0]) - hash_position[0] = std::fmod(hash_position[0], tiling[0]); - if (tiling[1]) - hash_position[1] = std::fmod(hash_position[1], tiling[1]); + vector hash_position = position_i + offset_i; + for (std::size_t j = 0; j < N; ++j) + { + if (tiling[j]) + hash_position[j] = std::fmod(hash_position[j], tiling[j]); + } // Calculate hash values for the hash position - vector hash_i = vector(hash(hash_position)); + vector hash_i = hash(hash_position); // Convert hash values to pseudorandom fractional offset - vector offset_f = vector(hash_i) * hash_scale; + vector offset_f = vector(hash_i) * hash_scale; // Calculate displacement from input position to cell center - vector displacement = (offset_i + offset_f) - position_f; + vector displacement = (offset_i + offset_f) - position_f; // Calculate square distance to the current cell center T sqr_distance = math::length_squared(displacement); @@ -151,6 +183,7 @@ f1 * Finds the Voronoi cell (F1) containing the input position, along with the distance to the nearest edge. * * @tparam T Real type. + * @tparam N Number of dimensions. * @tparam U Hash function return type. * * @param position Input position. @@ -160,14 +193,14 @@ f1 * * @return Tuple containing the square Euclidean distance from @p position to the F1 cell center, the displacement vector from the input position to the F1 cell center, a hash value indicating the ID of the F1 cell, and the square Euclidean distance from @p position to the nearest edge. */ -template +template std::tuple < // F1 square distance to center T, // F1 position to center displacement - vector, + vector, // F1 hash U, @@ -177,93 +210,85 @@ std::tuple > f1_edge ( - const vector& position, + const vector& position, T randomness, - const vector& tiling, - vector (*hash)(const vector&) + const vector& tiling, + vector (*hash)(const vector&) ) { // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness T hash_scale = (T{1} / static_cast(std::numeric_limits::max())) * randomness; // Get integer and fractional parts - vector position_i = math::floor(position - T{1.5}); - vector position_f = position - position_i; + vector position_i = math::floor(position - T{1.5}); + vector position_f = position - position_i; // Find F1 cell - T f1_sqr_distance_center = f1_max_sqr_distance; - vector displacement_cache[4][4]; - int f1_i = 0; - int f1_j = 0; + T f1_sqr_distance_center = std::numeric_limits::infinity(); + vector displacement_cache[kernel_size]; + std::size_t f1_i = 0; U f1_hash; - vector offset_i; - for (int i = 0; i < 4; ++i) + + for (std::size_t i = 0; i < kernel_size; ++i) { - offset_i[0] = static_cast(i); + // Get kernel offset for current cell + const vector& offset_i = kernel[i]; - for (int j = 0; j < 4; ++j) + // Calculate hash input position, tiling where specified + vector hash_position = position_i + offset_i; + for (std::size_t j = 0; j < N; ++j) { - offset_i[1] = static_cast(j); - - // Calculate hash input position, tiling where specified - vector hash_position = position_i + offset_i; - if (tiling[0]) - hash_position[0] = std::fmod(hash_position[0], tiling[0]); - if (tiling[1]) - hash_position[1] = std::fmod(hash_position[1], tiling[1]); - - // Calculate hash values for the hash position - vector hash_i = vector(hash(hash_position)); - - // Convert hash values to pseudorandom fractional offset - vector offset_f = vector(hash_i) * hash_scale; - - // Calculate and cache displacement from input position to cell center - displacement_cache[i][j] = (offset_i + offset_f) - position_f; - - // Calculate square distance to the current cell center - T sqr_distance = math::length_squared(displacement_cache[i][j]); - - // Update F1 cell - if (sqr_distance < f1_sqr_distance_center) - { - f1_sqr_distance_center = sqr_distance; - f1_i = i; - f1_j = j; - f1_hash = hash_i[0]; - } + if (tiling[j]) + hash_position[j] = std::fmod(hash_position[j], tiling[j]); + } + + // Calculate hash values for the hash position + vector hash_i = hash(hash_position); + + // Convert hash values to pseudorandom fractional offset + vector offset_f = vector(hash_i) * hash_scale; + + // Calculate and cache displacement from input position to cell center + displacement_cache[i] = (offset_i + offset_f) - position_f; + + // Calculate square distance to the current cell center + T sqr_distance = math::length_squared(displacement_cache[i]); + + // Update F1 cell + if (sqr_distance < f1_sqr_distance_center) + { + f1_sqr_distance_center = sqr_distance; + f1_i = i; + f1_hash = hash_i[0]; } } // Get displacement vector from input position to the F1 cell center - const vector& f1_displacement = displacement_cache[f1_i][f1_j]; + const vector& f1_displacement = displacement_cache[f1_i]; // Find distance to the closest edge - T edge_sqr_distance_edge = f1_max_sqr_distance; - for (int i = 0; i < 4; ++i) + T edge_sqr_distance_edge = std::numeric_limits::infinity(); + for (std::size_t i = 0; i < kernel_size; ++i) { - for (int j = 0; j < 4; ++j) - { - // Skip F1 cell - if (j == f1_j && i == f1_i) - continue; - - // Fetch cached displacement vector for current cell - const vector& displacement = displacement_cache[i][j]; - - // Find midpoint between the displacement vectors - const vector midpoint = (f1_displacement + displacement) * T{0.5}; - - // Calculate direction from the F1 cell to current cell - const vector direction = math::normalize(displacement - f1_displacement); - - // Calculate square distance to the edge - const T sqr_distance = math::dot(midpoint, direction); - - // Update minimum edge distance if closer than the nearest edge - if (sqr_distance < edge_sqr_distance_edge) - edge_sqr_distance_edge = sqr_distance; - } + // Skip F1 cell + if (i == f1_i) + continue; + + // Fetch cached displacement vector for current cell + const vector& displacement = displacement_cache[i]; + + // Find midpoint between the displacement vectors + const vector midpoint = (f1_displacement + displacement) * T{0.5}; + + // Calculate direction from the F1 cell to current cell + const vector direction = math::normalize(displacement - f1_displacement); + + // Calculate square distance to the edge + const T sqr_distance = math::dot(midpoint, direction); + + // Update minimum edge distance if closer than the nearest edge + if (sqr_distance < edge_sqr_distance_edge) + edge_sqr_distance_edge = sqr_distance; } return @@ -279,6 +304,7 @@ f1_edge * Finds the Voronoi cell (F1) containing the input position, as well as the nearest neighboring cell (F2). * * @tparam T Real type. + * @tparam N Number of dimensions. * @tparam U Hash function return type. * * @param position Input position. @@ -288,14 +314,14 @@ f1_edge * * @return Tuple containing the square Euclidean distances, displacement vectors from the input position to the cell centers, and hash values indicating the cell IDs, for both the F1 and F2 cells. */ -template +template std::tuple < // F1 square distance to center T, // F1 position to center displacement - vector, + vector, // F1 hash U, @@ -304,78 +330,75 @@ std::tuple T, // F2 position to center displacement - vector, + vector, // F2 hash U > f1_f2 ( - const vector& position, + const vector& position, T randomness, - const vector& tiling, - vector (*hash)(const vector&) + const vector& tiling, + vector (*hash)(const vector&) ) { // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness T hash_scale = (T{1} / static_cast(std::numeric_limits::max())) * randomness; // Get integer and fractional parts - vector position_i = math::floor(position - T{1.5}); - vector position_f = position - position_i; + vector position_i = math::floor(position - T{1.5}); + vector position_f = position - position_i; // Find the F1 and F2 cells - T f1_sqr_distance_center = f1_max_sqr_distance; - vector f1_displacement = {0, 0}; + T f1_sqr_distance_center = std::numeric_limits::infinity(); + vector f1_displacement = {0, 0}; U f1_hash = 0; - T f2_sqr_distance_center = f1_max_sqr_distance; - vector f2_displacement = {0, 0}; + T f2_sqr_distance_center = std::numeric_limits::infinity(); + vector f2_displacement = {0, 0}; U f2_hash = 0; - vector offset_i; - for (int i = 0; i < 4; ++i) + + for (std::size_t i = 0; i < kernel_size; ++i) { - offset_i[0] = static_cast(i); + // Get kernel offset for current cell + const vector& offset_i = kernel[i]; - for (int j = 0; j < 4; ++j) + // Calculate hash input position, tiling where specified + vector hash_position = position_i + offset_i; + for (std::size_t j = 0; j < N; ++j) { - offset_i[1] = static_cast(j); - - // Calculate hash input position, tiling where specified - vector hash_position = position_i + offset_i; - if (tiling[0]) - hash_position[0] = std::fmod(hash_position[0], tiling[0]); - if (tiling[1]) - hash_position[1] = std::fmod(hash_position[1], tiling[1]); - - // Calculate hash values for the hash position - vector hash_i = vector(hash(hash_position)); - - // Convert hash values to pseudorandom fractional offset - vector offset_f = vector(hash_i) * hash_scale; - - // Calculate displacement from input position to cell center - vector displacement = (offset_i + offset_f) - position_f; - - // Calculate square distance to the current cell center - T sqr_distance = math::length_squared(displacement); + if (tiling[j]) + hash_position[j] = std::fmod(hash_position[j], tiling[j]); + } + + // Calculate hash values for the hash position + vector hash_i = hash(hash_position); + + // Convert hash values to pseudorandom fractional offset + vector offset_f = vector(hash_i) * hash_scale; + + // Calculate displacement from input position to cell center + vector displacement = (offset_i + offset_f) - position_f; + + // Calculate square distance to the current cell center + T sqr_distance = math::length_squared(displacement); + + // Update F1 and F2 cells + if (sqr_distance < f1_sqr_distance_center) + { + f2_sqr_distance_center = f1_sqr_distance_center; + f2_displacement = f1_displacement; + f2_hash = f1_hash; - // Update F1 and F2 cells - if (sqr_distance < f1_sqr_distance_center) - { - f2_sqr_distance_center = f1_sqr_distance_center; - f2_displacement = f1_displacement; - f2_hash = f1_hash; - - f1_sqr_distance_center = sqr_distance; - f1_displacement = displacement; - f1_hash = hash_i[0]; - } - else if (sqr_distance < f2_sqr_distance_center) - { - f2_sqr_distance_center = sqr_distance; - f2_displacement = displacement; - f2_hash = hash_i[0]; - } + f1_sqr_distance_center = sqr_distance; + f1_displacement = displacement; + f1_hash = hash_i[0]; + } + else if (sqr_distance < f2_sqr_distance_center) + { + f2_sqr_distance_center = sqr_distance; + f2_displacement = displacement; + f2_hash = hash_i[0]; } }