💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

417 lines
12 KiB

  1. /*
  2. * Copyright (C) 2021 Christopher J. Howard
  3. *
  4. * This file is part of Antkeeper source code.
  5. *
  6. * Antkeeper source code is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Antkeeper source code is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #ifndef ANTKEEPER_MATH_NOISE_VORONOI_HPP
  20. #define ANTKEEPER_MATH_NOISE_VORONOI_HPP
  21. #include "math/vector.hpp"
  22. #include "math/hash/make-uint.hpp"
  23. #include "math/hash/pcg.hpp"
  24. #include <algorithm>
  25. #include <array>
  26. #include <cmath>
  27. #include <tuple>
  28. #include <limits>
  29. #include <utility>
  30. namespace math {
  31. namespace noise {
  32. /// Voronoi functions.
  33. namespace voronoi {
  34. /**
  35. * Number of Voronoi cells to search.
  36. *
  37. * @tparam N Number of dimensions.
  38. *
  39. * @private
  40. */
  41. template <std::size_t N>
  42. constexpr std::size_t kernel_size = 4 << std::max<std::size_t>(0, (2 * (N - 1)));
  43. /**
  44. * Generates an kernel offset vector for a given index.
  45. *
  46. * @tparam T Real type.
  47. * @tparam N Number of dimensions.
  48. * @tparam I Index sequence.
  49. *
  50. * @param i Index of a kernel offset vector.
  51. *
  52. * @return Kernel offset vector.
  53. *
  54. * @private
  55. */
  56. template <class T, std::size_t N, std::size_t... I>
  57. constexpr vector<T, N> kernel_offset(std::size_t i, std::index_sequence<I...>)
  58. {
  59. return {static_cast<T>((I ? (i / (2 << std::max<std::size_t>(0, 2 * I - 1))) : i) % 4)...};
  60. }
  61. /**
  62. * Generates a Voronoi search kernel.
  63. *
  64. * @tparam T Real type.
  65. * @tparam N Number of dimensions.
  66. * @tparam I Index sequence.
  67. *
  68. * @return Voronoi search kernel.
  69. *
  70. * @private
  71. */
  72. template <class T, std::size_t N, std::size_t... I>
  73. constexpr std::array<vector<T, N>, kernel_size<N>> generate_kernel(std::index_sequence<I...>)
  74. {
  75. return {kernel_offset<T, N>(I, std::make_index_sequence<N>{})...};
  76. }
  77. /**
  78. * *n*-dimensional search kernel.
  79. *
  80. * @tparam T Real type.
  81. * @tparam N Number of dimensions.
  82. *
  83. * @private
  84. */
  85. template <class T, std::size_t N>
  86. constexpr auto kernel = generate_kernel<T, N>(std::make_index_sequence<kernel_size<N>>{});
  87. /**
  88. * Finds the Voronoi cell (F1) containing the input position.
  89. *
  90. * @tparam T Real type.
  91. * @tparam N Number of dimensions.
  92. *
  93. * @param position Input position.
  94. * @param randomness Degree of randomness, on `[0, 1]`.
  95. * @param tiling Distance at which the Voronoi pattern should repeat. A value of `0` indicates no repetition.
  96. * @param hash Hash function.
  97. *
  98. * @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.
  99. */
  100. template <class T, std::size_t N>
  101. std::tuple
  102. <
  103. // F1 square distance to center
  104. T,
  105. // F1 position to center displacement
  106. vector<T, N>,
  107. // F1 hash
  108. hash::make_uint_t<T>
  109. >
  110. f1
  111. (
  112. const vector<T, N>& position,
  113. T randomness = T{1},
  114. const vector<T, N>& tiling = vector<T, N>::zero(),
  115. vector<hash::make_uint_t<T>, N> (*hash)(const vector<T, N>&) = &hash::pcg<T, N>
  116. )
  117. {
  118. // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
  119. T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<hash::make_uint_t<T>>::max())) * randomness;
  120. // Get integer and fractional parts
  121. vector<T, N> position_i = floor(position - T{1.5});
  122. vector<T, N> position_f = position - position_i;
  123. // Find the F1 cell
  124. T f1_sqr_distance = std::numeric_limits<T>::infinity();
  125. vector<T, N> f1_displacement;
  126. hash::make_uint_t<T> f1_hash;
  127. for (std::size_t i = 0; i < kernel_size<N>; ++i)
  128. {
  129. // Get kernel offset for current cell
  130. const vector<T, N>& offset_i = kernel<T, N>[i];
  131. // Calculate hash input position, tiling where specified
  132. vector<T, N> hash_position = position_i + offset_i;
  133. for (std::size_t j = 0; j < N; ++j)
  134. {
  135. if (tiling[j])
  136. hash_position[j] = std::fmod(hash_position[j], tiling[j]);
  137. }
  138. // Calculate hash values for the hash position
  139. vector<hash::make_uint_t<T>, N> hash_i = hash(hash_position);
  140. // Convert hash values to pseudorandom fractional offset
  141. vector<T, N> offset_f = vector<T, N>(hash_i) * hash_scale;
  142. // Calculate displacement from input position to cell center
  143. vector<T, N> displacement = (offset_i + offset_f) - position_f;
  144. // Calculate square distance to the current cell center
  145. T sqr_distance = sqr_length(displacement);
  146. // Update F1 cell
  147. if (sqr_distance < f1_sqr_distance)
  148. {
  149. f1_sqr_distance = sqr_distance;
  150. f1_displacement = displacement;
  151. f1_hash = hash_i[0];
  152. }
  153. }
  154. return
  155. {
  156. f1_sqr_distance,
  157. f1_displacement,
  158. f1_hash
  159. };
  160. }
  161. /**
  162. * Finds the Voronoi cell (F1) containing the input position, along with the distance to the nearest edge.
  163. *
  164. * @tparam T Real type.
  165. * @tparam N Number of dimensions.
  166. *
  167. * @param position Input position.
  168. * @param randomness Degree of randomness, on `[0, 1]`.
  169. * @param tiling Distance at which the Voronoi pattern should repeat. A value of `0` indicates no repetition.
  170. * @param hash Hash function.
  171. *
  172. * @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.
  173. */
  174. template <class T, std::size_t N>
  175. std::tuple
  176. <
  177. // F1 square distance to center
  178. T,
  179. // F1 position to center displacement
  180. vector<T, N>,
  181. // F1 hash
  182. hash::make_uint_t<T>,
  183. // Edge square distance
  184. T
  185. >
  186. f1_edge
  187. (
  188. const vector<T, N>& position,
  189. T randomness = T{1},
  190. const vector<T, N>& tiling = vector<T, N>::zero(),
  191. vector<hash::make_uint_t<T>, N> (*hash)(const vector<T, N>&) = &hash::pcg<T, N>
  192. )
  193. {
  194. // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
  195. T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<hash::make_uint_t<T>>::max())) * randomness;
  196. // Get integer and fractional parts
  197. vector<T, N> position_i = floor(position - T{1.5});
  198. vector<T, N> position_f = position - position_i;
  199. // Find F1 cell
  200. T f1_sqr_distance_center = std::numeric_limits<T>::infinity();
  201. vector<T, N> displacement_cache[kernel_size<N>];
  202. std::size_t f1_i = 0;
  203. hash::make_uint_t<T> f1_hash;
  204. for (std::size_t i = 0; i < kernel_size<N>; ++i)
  205. {
  206. // Get kernel offset for current cell
  207. const vector<T, N>& offset_i = kernel<T, N>[i];
  208. // Calculate hash input position, tiling where specified
  209. vector<T, N> hash_position = position_i + offset_i;
  210. for (std::size_t j = 0; j < N; ++j)
  211. {
  212. if (tiling[j])
  213. hash_position[j] = std::fmod(hash_position[j], tiling[j]);
  214. }
  215. // Calculate hash values for the hash position
  216. vector<hash::make_uint_t<T>, N> hash_i = hash(hash_position);
  217. // Convert hash values to pseudorandom fractional offset
  218. vector<T, N> offset_f = vector<T, N>(hash_i) * hash_scale;
  219. // Calculate and cache displacement from input position to cell center
  220. displacement_cache[i] = (offset_i + offset_f) - position_f;
  221. // Calculate square distance to the current cell center
  222. T sqr_distance = sqr_length(displacement_cache[i]);
  223. // Update F1 cell
  224. if (sqr_distance < f1_sqr_distance_center)
  225. {
  226. f1_sqr_distance_center = sqr_distance;
  227. f1_i = i;
  228. f1_hash = hash_i[0];
  229. }
  230. }
  231. // Get displacement vector from input position to the F1 cell center
  232. const vector<T, N>& f1_displacement = displacement_cache[f1_i];
  233. // Find distance to the closest edge
  234. T edge_sqr_distance_edge = std::numeric_limits<T>::infinity();
  235. for (std::size_t i = 0; i < kernel_size<N>; ++i)
  236. {
  237. // Skip F1 cell
  238. if (i == f1_i)
  239. continue;
  240. // Fetch cached displacement vector for current cell
  241. const vector<T, N>& displacement = displacement_cache[i];
  242. // Find midpoint between the displacement vectors
  243. const vector<T, N> midpoint = (f1_displacement + displacement) * T{0.5};
  244. // Calculate direction from the F1 cell to current cell
  245. const vector<T, N> direction = normalize(displacement - f1_displacement);
  246. // Calculate square distance to the edge
  247. const T sqr_distance = dot(midpoint, direction);
  248. // Update minimum edge distance if closer than the nearest edge
  249. if (sqr_distance < edge_sqr_distance_edge)
  250. edge_sqr_distance_edge = sqr_distance;
  251. }
  252. return
  253. {
  254. f1_sqr_distance_center,
  255. f1_displacement,
  256. f1_hash,
  257. edge_sqr_distance_edge
  258. };
  259. }
  260. /**
  261. * Finds the Voronoi cell (F1) containing the input position, as well as the nearest neighboring cell (F2).
  262. *
  263. * @tparam T Real type.
  264. * @tparam N Number of dimensions.
  265. *
  266. * @param position Input position.
  267. * @param randomness Degree of randomness, on `[0, 1]`.
  268. * @param tiling Distance at which the Voronoi pattern should repeat. A value of `0` indicates no repetition.
  269. * @param hash Hash function.
  270. *
  271. * @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.
  272. */
  273. template <class T, std::size_t N>
  274. std::tuple
  275. <
  276. // F1 square distance to center
  277. T,
  278. // F1 position to center displacement
  279. vector<T, N>,
  280. // F1 hash
  281. hash::make_uint_t<T>,
  282. // F2 square distance to center
  283. T,
  284. // F2 position to center displacement
  285. vector<T, N>,
  286. // F2 hash
  287. hash::make_uint_t<T>
  288. >
  289. f1_f2
  290. (
  291. const vector<T, N>& position,
  292. T randomness = T{1},
  293. const vector<T, N>& tiling = vector<T, N>::zero(),
  294. vector<hash::make_uint_t<T>, N> (*hash)(const vector<T, N>&) = &hash::pcg<T, N>
  295. )
  296. {
  297. // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
  298. T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<hash::make_uint_t<T>>::max())) * randomness;
  299. // Get integer and fractional parts
  300. vector<T, N> position_i = floor(position - T{1.5});
  301. vector<T, N> position_f = position - position_i;
  302. // Find the F1 and F2 cells
  303. T f1_sqr_distance_center = std::numeric_limits<T>::infinity();
  304. vector<T, N> f1_displacement = {0, 0};
  305. hash::make_uint_t<T> f1_hash = 0;
  306. T f2_sqr_distance_center = std::numeric_limits<T>::infinity();
  307. vector<T, N> f2_displacement = {0, 0};
  308. hash::make_uint_t<T> f2_hash = 0;
  309. for (std::size_t i = 0; i < kernel_size<N>; ++i)
  310. {
  311. // Get kernel offset for current cell
  312. const vector<T, N>& offset_i = kernel<T, N>[i];
  313. // Calculate hash input position, tiling where specified
  314. vector<T, N> hash_position = position_i + offset_i;
  315. for (std::size_t j = 0; j < N; ++j)
  316. {
  317. if (tiling[j])
  318. hash_position[j] = std::fmod(hash_position[j], tiling[j]);
  319. }
  320. // Calculate hash values for the hash position
  321. vector<hash::make_uint_t<T>, N> hash_i = hash(hash_position);
  322. // Convert hash values to pseudorandom fractional offset
  323. vector<T, N> offset_f = vector<T, N>(hash_i) * hash_scale;
  324. // Calculate displacement from input position to cell center
  325. vector<T, N> displacement = (offset_i + offset_f) - position_f;
  326. // Calculate square distance to the current cell center
  327. T sqr_distance = sqr_length(displacement);
  328. // Update F1 and F2 cells
  329. if (sqr_distance < f1_sqr_distance_center)
  330. {
  331. f2_sqr_distance_center = f1_sqr_distance_center;
  332. f2_displacement = f1_displacement;
  333. f2_hash = f1_hash;
  334. f1_sqr_distance_center = sqr_distance;
  335. f1_displacement = displacement;
  336. f1_hash = hash_i[0];
  337. }
  338. else if (sqr_distance < f2_sqr_distance_center)
  339. {
  340. f2_sqr_distance_center = sqr_distance;
  341. f2_displacement = displacement;
  342. f2_hash = hash_i[0];
  343. }
  344. }
  345. return
  346. {
  347. f1_sqr_distance_center,
  348. f1_displacement,
  349. f1_hash,
  350. f2_sqr_distance_center,
  351. f2_displacement,
  352. f2_hash,
  353. };
  354. }
  355. } // namespace voronoi
  356. } // namespace noise
  357. } // namespace math
  358. #endif // ANTKEEPER_MATH_NOISE_VORONOI_HPP