diff --git a/src/game/load.cpp b/src/game/load.cpp index adf17d6..5ee7dc4 100644 --- a/src/game/load.cpp +++ b/src/game/load.cpp @@ -98,14 +98,31 @@ void biome(game::context& ctx, const std::filesystem::path& path) }; //float n = math::noise::simplex(position, &math::noise::hash::pcg3d_1); - float n = fbm(position); + //float n = fbm(position); + // auto [sqr_center_distance, displacement, id, sqr_edge_distance] = math::noise::voronoi::f1_edge(position, 1.0f, &math::noise::hash::pcg3d_3); + // float center_distance = std::sqrt(sqr_center_distance); + // float edge_distance = std::sqrt(sqr_edge_distance); - pixel = static_cast((n * 0.5f + 0.5f) * 255.0f); + auto + [ + 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); + + float f1_distance = std::sqrt(f1_sqr_distance); + float f2_distance = std::sqrt(f2_sqr_distance); + + pixel = static_cast(std::min(255.0f, f1_distance * 255.0f)); + //pixel = static_cast(id % 255); } ); stbi_flip_vertically_on_write(1); - stbi_write_png((ctx.config_path / "gallery" / "simplex-noise.png").string().c_str(), img.get_width(), img.get_height(), img.get_channel_count(), img.data(), img.get_width() * img.get_channel_count()); + stbi_write_png((ctx.config_path / "gallery" / "noise.png").string().c_str(), img.get_width(), img.get_height(), img.get_channel_count(), img.data(), img.get_width() * img.get_channel_count()); try @@ -192,6 +209,7 @@ void biome(game::context& ctx, const std::filesystem::path& path) float2 position = float2{x, z} * frequency; + /* float n = math::noise::fbm ( position, @@ -201,8 +219,10 @@ void biome(game::context& ctx, const std::filesystem::path& path) noise, hash ); - - return 2.0f * n; + */ + //float n = math::noise::voronoi::f1(position, 1.0f, &math::noise::hash::pcg3d_3)[0]; + float n = 0.0f; + return 10.0f * n; } ); diff --git a/src/math/noise/fbm.hpp b/src/math/noise/fbm.hpp index c3abbe9..c7a416a 100644 --- a/src/math/noise/fbm.hpp +++ b/src/math/noise/fbm.hpp @@ -21,7 +21,7 @@ #define ANTKEEPER_MATH_NOISE_FBM_HPP #include "math/vector.hpp" -#include +#include namespace math { namespace noise { @@ -31,6 +31,7 @@ namespace noise { * * @tparam T Real type. * @tparam N Number of dimensions. + * @tparam U Hash function return type. * * @param x n-dimensional input value. * @param octaves Number of octaves. @@ -40,15 +41,15 @@ namespace noise { * @param hash Hash function. * */ -template +template T fbm ( vector x, std::size_t octaves, T lacunarity, T gain, - T (*noise)(const vector&, std::uint32_t (*)(const vector&)), - std::uint32_t (*hash)(const vector&) + T (*noise)(const vector&, U (*)(const vector&)), + U (*hash)(const vector&) ) { T amplitude{1}; diff --git a/src/math/noise/noise.hpp b/src/math/noise/noise.hpp index 0c13180..8f076ff 100644 --- a/src/math/noise/noise.hpp +++ b/src/math/noise/noise.hpp @@ -30,5 +30,6 @@ namespace noise {} #include "math/noise/fbm.hpp" #include "math/noise/hash.hpp" #include "math/noise/simplex.hpp" +#include "math/noise/voronoi.hpp" #endif // ANTKEEPER_MATH_NOISE_HPP diff --git a/src/math/noise/simplex.hpp b/src/math/noise/simplex.hpp index 36d5f75..9919352 100644 --- a/src/math/noise/simplex.hpp +++ b/src/math/noise/simplex.hpp @@ -96,6 +96,7 @@ constexpr auto simplex_edges = make_simplex_edges(std::make_index_sequence * * @tparam T Real type. * @tparam N Number of dimensions. + * @tparam U Hash function return type. * * @param x Input vector. * @param hash Hash function. @@ -108,8 +109,8 @@ constexpr auto simplex_edges = make_simplex_edges(std::make_index_sequence * @see https://briansharpe.wordpress.com/2011/11/14/two-useful-interpolation-functions-for-noise-development/ * @see https://math.stackexchange.com/questions/474638/radius-and-amplitude-of-kernel-for-simplex-noise/1901116 */ -template -T simplex(const vector& x, std::uint32_t (*hash)(const vector&)) +template +T simplex(const vector& x, U (*hash)(const vector&)) { // Skewing (F) and unskewing (G) factors static const T f = (std::sqrt(static_cast(N + 1)) - T{1}) / static_cast(N); diff --git a/src/math/noise/voronoi.hpp b/src/math/noise/voronoi.hpp new file mode 100644 index 0000000..d8465dd --- /dev/null +++ b/src/math/noise/voronoi.hpp @@ -0,0 +1,398 @@ +/* + * Copyright (C) 2021 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_MATH_NOISE_VORONOI_HPP +#define ANTKEEPER_MATH_NOISE_VORONOI_HPP + +#include "math/vector.hpp" +#include +#include +#include +#include + +namespace math { +namespace noise { + +/// Voronoi functions. +namespace voronoi { + +/** + * Number of neighboring cells that must be searched for F1. + * + * @private + */ +constexpr std::size_t f1_kernel_size = 12; + +/** + * Offsets to neighboring cells that must be searched for F1. + * + * @private + */ +template +constexpr vector f1_kernel[f1_kernel_size] = +{ + /*****/ {1, 0}, {2, 0}, /*****/ + {0, 1}, {1, 1}, {2, 1}, {3, 1}, + {0, 2}, {1, 2}, {2, 2}, {3, 2}, + /*****/ {1, 3}, {2, 3} /*****/ +}; + +/** + * Maximum squared distance to the nearest F1 cell center. + * + * @private + */ +template +constexpr T f1_max_sqr_distance = T{8}; + +/** + * Finds the Voronoi cell (F1) containing the input position. + * + * @tparam T Real type. + * @tparam U Hash function return type. + * + * @param position Input position. + * @param randomness Degree of randomness, on `[0, 1]`. + * @param tiling Distance at which the Voronoi pattern should repeat. A value of `0` indicates no repetition. + * @param hash Hash function. + * + * @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 +std::tuple +< + // F1 square distance to center + T, + + // F1 position to center displacement + vector, + + // F1 hash + U +> +f1 +( + const vector& position, + T randomness, + 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; + + // Find the F1 cell + T f1_sqr_distance = f1_max_sqr_distance; + vector f1_displacement; + U f1_hash; + + for (std::size_t i = 0; i < f1_kernel_size; ++i) + { + // Get kernel offset for current cell + const vector& offset_i = f1_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]); + + // 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); + + // Update F1 cell + if (sqr_distance < f1_sqr_distance) + { + f1_sqr_distance = sqr_distance; + f1_displacement = displacement; + f1_hash = hash_i[0]; + } + } + + return + { + f1_sqr_distance, + f1_displacement, + f1_hash + }; +} + +/** + * Finds the Voronoi cell (F1) containing the input position, along with the distance to the nearest edge. + * + * @tparam T Real type. + * @tparam U Hash function return type. + * + * @param position Input position. + * @param randomness Degree of randomness, on `[0, 1]`. + * @param tiling Distance at which the Voronoi pattern should repeat. A value of `0` indicates no repetition. + * @param hash Hash function. + * + * @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 +std::tuple +< + // F1 square distance to center + T, + + // F1 position to center displacement + vector, + + // F1 hash + U, + + // Edge square distance + T +> +f1_edge +( + const vector& position, + T randomness, + 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; + + // 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; + U f1_hash; + vector offset_i; + for (int i = 0; i < 4; ++i) + { + offset_i[0] = static_cast(i); + + for (int j = 0; j < 4; ++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]; + } + } + } + + // Get displacement vector from input position to the F1 cell center + const vector& f1_displacement = displacement_cache[f1_i][f1_j]; + + // Find distance to the closest edge + T edge_sqr_distance_edge = f1_max_sqr_distance; + for (int i = 0; i < 4; ++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; + } + } + + return + { + f1_sqr_distance_center, + f1_displacement, + f1_hash, + edge_sqr_distance_edge + }; +} + +/** + * Finds the Voronoi cell (F1) containing the input position, as well as the nearest neighboring cell (F2). + * + * @tparam T Real type. + * @tparam U Hash function return type. + * + * @param position Input position. + * @param randomness Degree of randomness, on `[0, 1]`. + * @param tiling Distance at which the Voronoi pattern should repeat. A value of `0` indicates no repetition. + * @param hash Hash function. + * + * @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 +std::tuple +< + // F1 square distance to center + T, + + // F1 position to center displacement + vector, + + // F1 hash + U, + + // F2 square distance to center + T, + + // F2 position to center displacement + vector, + + // F2 hash + U +> +f1_f2 +( + const vector& position, + T randomness, + 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; + + // Find the F1 and F2 cells + T f1_sqr_distance_center = f1_max_sqr_distance; + vector f1_displacement = {0, 0}; + U f1_hash = 0; + T f2_sqr_distance_center = f1_max_sqr_distance; + vector f2_displacement = {0, 0}; + U f2_hash = 0; + vector offset_i; + for (int i = 0; i < 4; ++i) + { + offset_i[0] = static_cast(i); + + for (int j = 0; j < 4; ++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); + + // 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]; + } + } + } + + return + { + f1_sqr_distance_center, + f1_displacement, + f1_hash, + f2_sqr_distance_center, + f2_displacement, + f2_hash, + }; +} + +} // namespace voronoi + +} // namespace noise +} // namespace math + +#endif // ANTKEEPER_MATH_NOISE_VORONOI_HPP diff --git a/src/math/vector.hpp b/src/math/vector.hpp index 4cd38e3..8a58e4b 100644 --- a/src/math/vector.hpp +++ b/src/math/vector.hpp @@ -223,7 +223,7 @@ struct vector } /** - * Type-casts the elements of this vector. + * Type-casts the elements of this vector using `static_cast`. * * @tparam U Target element type. * @@ -512,6 +512,17 @@ constexpr vector less_than(const vector& x, const vector& y template constexpr vector less_than_equal(const vector& x, const vector& y); +/** + * Returns a vector containing the maximum elements of two vectors. + * + * @param x First vector. + * @param y Second vector. + * + * @return Maximum elements of the two vectors. + */ +template +constexpr vector max(const vector& x, const vector& y); + /** * Returns the value of the greatest element in a vector. * @@ -522,6 +533,17 @@ constexpr vector less_than_equal(const vector& x, const vector constexpr T max(const vector& x); +/** + * Returns a vector containing the minimum elements of two vectors. + * + * @param x First vector. + * @param y Second vector. + * + * @return Minimum elements of the two vectors. + */ +template +constexpr vector min(const vector& x, const vector& y); + /** * Returns the value of the smallest element in a vector. * @@ -597,6 +619,21 @@ constexpr vector not(const vector& x); template constexpr vector not_equal(const vector& x, const vector& y); +/** + * Raises each element to a power. + * + * @param x Input vector + * @param y Exponent. + * + * @return Vector with its elements raised to the given power. + */ +/// @{ +template +vector pow(const vector& x, const vector& y); +template +vector pow(const vector& x, T y); +/// @} + /** * Resizes a vector. Any new elements will be set to `0`. * @@ -627,6 +664,16 @@ constexpr vector round(const vector& x); template constexpr vector sign(const vector& x); +/** + * Takes the square root of each element. + * + * @param x Input vector + * + * @return Square roots of the input vector elements. + */ +template +vector sqrt(const vector& x); + /** * Subtracts a value by another value. * @@ -1004,12 +1051,38 @@ constexpr inline vector less_than_equal(const vector& x, const ve return less_than_equal(x, y, std::make_index_sequence{}); } +/// @private +template +constexpr inline vector max(const vector& x, const vector& y, std::index_sequence) +{ + return {std::max(x[I], y[I])...}; +} + +template +constexpr vector max(const vector& x, const vector& y) +{ + return max(x, y, std::make_index_sequence{}); +} + template constexpr inline T max(const vector& x) { return *std::max_element(x.elements, x.elements + N); } +/// @private +template +constexpr inline vector min(const vector& x, const vector& y, std::index_sequence) +{ + return {std::min(x[I], y[I])...}; +} + +template +constexpr vector min(const vector& x, const vector& y) +{ + return min(x, y, std::make_index_sequence{}); +} + template constexpr inline T min(const vector& x) { @@ -1116,6 +1189,34 @@ constexpr inline vector not_equal(const vector& x, const vector{}); } +/// @private +template +inline vector pow(const vector& x, const vector& y, std::index_sequence) +{ + return {std::pow(x[I], y[I])...}; +} + +template +inline vector pow(const vector& x, const vector& y) +{ + static_assert(std::is_floating_point::value); + return pow(x, y, std::make_index_sequence{}); +} + +/// @private +template +inline vector pow(const vector& x, T y, std::index_sequence) +{ + return {std::pow(x[I], y)...}; +} + +template +inline vector pow(const vector& x, T y) +{ + static_assert(std::is_floating_point::value); + return pow(x, y, std::make_index_sequence{}); +} + /// @private template constexpr inline vector resize(const vector& x, std::index_sequence) @@ -1157,6 +1258,20 @@ constexpr inline vector sign(const vector& x) return sign(x, std::make_index_sequence{}); } +/// @private +template +inline vector sqrt(const vector& x, std::index_sequence) +{ + return {std::sqrt(x[I])...}; +} + +template +inline vector sqrt(const vector& x, const vector& y) +{ + static_assert(std::is_floating_point::value); + return pow(x, std::make_index_sequence{}); +} + /// @private template constexpr inline vector sub(const vector& x, const vector& y, std::index_sequence)