💿🐜 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.

429 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. {
  137. hash_position[j] = std::fmod(hash_position[j], tiling[j]);
  138. if (hash_position[j] < T{0})
  139. hash_position[j] += tiling[j];
  140. }
  141. }
  142. // Calculate hash values for the hash position
  143. vector<hash::make_uint_t<T>, N> hash_i = hash(hash_position);
  144. // Convert hash values to pseudorandom fractional offset
  145. vector<T, N> offset_f = vector<T, N>(hash_i) * hash_scale;
  146. // Calculate displacement from input position to cell center
  147. vector<T, N> displacement = (offset_i + offset_f) - position_f;
  148. // Calculate square distance to the current cell center
  149. T sqr_distance = sqr_length(displacement);
  150. // Update F1 cell
  151. if (sqr_distance < f1_sqr_distance)
  152. {
  153. f1_sqr_distance = sqr_distance;
  154. f1_displacement = displacement;
  155. f1_hash = hash_i[0];
  156. }
  157. }
  158. return
  159. {
  160. f1_sqr_distance,
  161. f1_displacement,
  162. f1_hash
  163. };
  164. }
  165. /**
  166. * Finds the Voronoi cell (F1) containing the input position, along with the distance to the nearest edge.
  167. *
  168. * @tparam T Real type.
  169. * @tparam N Number of dimensions.
  170. *
  171. * @param position Input position.
  172. * @param randomness Degree of randomness, on `[0, 1]`.
  173. * @param tiling Distance at which the Voronoi pattern should repeat. A value of `0` indicates no repetition.
  174. * @param hash Hash function.
  175. *
  176. * @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.
  177. */
  178. template <class T, std::size_t N>
  179. std::tuple
  180. <
  181. // F1 square distance to center
  182. T,
  183. // F1 position to center displacement
  184. vector<T, N>,
  185. // F1 hash
  186. hash::make_uint_t<T>,
  187. // Edge square distance
  188. T
  189. >
  190. f1_edge
  191. (
  192. const vector<T, N>& position,
  193. T randomness = T{1},
  194. const vector<T, N>& tiling = vector<T, N>::zero(),
  195. vector<hash::make_uint_t<T>, N> (*hash)(const vector<T, N>&) = &hash::pcg<T, N>
  196. )
  197. {
  198. // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
  199. T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<hash::make_uint_t<T>>::max())) * randomness;
  200. // Get integer and fractional parts
  201. vector<T, N> position_i = floor(position - T{1.5});
  202. vector<T, N> position_f = position - position_i;
  203. // Find F1 cell
  204. T f1_sqr_distance_center = std::numeric_limits<T>::infinity();
  205. vector<T, N> displacement_cache[kernel_size<N>];
  206. std::size_t f1_i = 0;
  207. hash::make_uint_t<T> f1_hash;
  208. for (std::size_t i = 0; i < kernel_size<N>; ++i)
  209. {
  210. // Get kernel offset for current cell
  211. const vector<T, N>& offset_i = kernel<T, N>[i];
  212. // Calculate hash input position, tiling where specified
  213. vector<T, N> hash_position = position_i + offset_i;
  214. for (std::size_t j = 0; j < N; ++j)
  215. {
  216. if (tiling[j])
  217. {
  218. hash_position[j] = std::fmod(hash_position[j], tiling[j]);
  219. if (hash_position[j] < T{0})
  220. hash_position[j] += tiling[j];
  221. }
  222. }
  223. // Calculate hash values for the hash position
  224. vector<hash::make_uint_t<T>, N> hash_i = hash(hash_position);
  225. // Convert hash values to pseudorandom fractional offset
  226. vector<T, N> offset_f = vector<T, N>(hash_i) * hash_scale;
  227. // Calculate and cache displacement from input position to cell center
  228. displacement_cache[i] = (offset_i + offset_f) - position_f;
  229. // Calculate square distance to the current cell center
  230. T sqr_distance = sqr_length(displacement_cache[i]);
  231. // Update F1 cell
  232. if (sqr_distance < f1_sqr_distance_center)
  233. {
  234. f1_sqr_distance_center = sqr_distance;
  235. f1_i = i;
  236. f1_hash = hash_i[0];
  237. }
  238. }
  239. // Get displacement vector from input position to the F1 cell center
  240. const vector<T, N>& f1_displacement = displacement_cache[f1_i];
  241. // Find distance to the closest edge
  242. T edge_sqr_distance_edge = std::numeric_limits<T>::infinity();
  243. for (std::size_t i = 0; i < kernel_size<N>; ++i)
  244. {
  245. // Skip F1 cell
  246. if (i == f1_i)
  247. continue;
  248. // Fetch cached displacement vector for current cell
  249. const vector<T, N>& displacement = displacement_cache[i];
  250. // Find midpoint between the displacement vectors
  251. const vector<T, N> midpoint = (f1_displacement + displacement) * T{0.5};
  252. // Calculate direction from the F1 cell to current cell
  253. const vector<T, N> direction = normalize(displacement - f1_displacement);
  254. // Calculate square distance to the edge
  255. const T sqr_distance = dot(midpoint, direction);
  256. // Update minimum edge distance if closer than the nearest edge
  257. if (sqr_distance < edge_sqr_distance_edge)
  258. edge_sqr_distance_edge = sqr_distance;
  259. }
  260. return
  261. {
  262. f1_sqr_distance_center,
  263. f1_displacement,
  264. f1_hash,
  265. edge_sqr_distance_edge
  266. };
  267. }
  268. /**
  269. * Finds the Voronoi cell (F1) containing the input position, as well as the nearest neighboring cell (F2).
  270. *
  271. * @tparam T Real type.
  272. * @tparam N Number of dimensions.
  273. *
  274. * @param position Input position.
  275. * @param randomness Degree of randomness, on `[0, 1]`.
  276. * @param tiling Distance at which the Voronoi pattern should repeat. A value of `0` indicates no repetition.
  277. * @param hash Hash function.
  278. *
  279. * @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.
  280. */
  281. template <class T, std::size_t N>
  282. std::tuple
  283. <
  284. // F1 square distance to center
  285. T,
  286. // F1 position to center displacement
  287. vector<T, N>,
  288. // F1 hash
  289. hash::make_uint_t<T>,
  290. // F2 square distance to center
  291. T,
  292. // F2 position to center displacement
  293. vector<T, N>,
  294. // F2 hash
  295. hash::make_uint_t<T>
  296. >
  297. f1_f2
  298. (
  299. const vector<T, N>& position,
  300. T randomness = T{1},
  301. const vector<T, N>& tiling = vector<T, N>::zero(),
  302. vector<hash::make_uint_t<T>, N> (*hash)(const vector<T, N>&) = &hash::pcg<T, N>
  303. )
  304. {
  305. // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
  306. T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<hash::make_uint_t<T>>::max())) * randomness;
  307. // Get integer and fractional parts
  308. vector<T, N> position_i = floor(position - T{1.5});
  309. vector<T, N> position_f = position - position_i;
  310. // Find the F1 and F2 cells
  311. T f1_sqr_distance_center = std::numeric_limits<T>::infinity();
  312. vector<T, N> f1_displacement = {0, 0};
  313. hash::make_uint_t<T> f1_hash = 0;
  314. T f2_sqr_distance_center = std::numeric_limits<T>::infinity();
  315. vector<T, N> f2_displacement = {0, 0};
  316. hash::make_uint_t<T> f2_hash = 0;
  317. for (std::size_t i = 0; i < kernel_size<N>; ++i)
  318. {
  319. // Get kernel offset for current cell
  320. const vector<T, N>& offset_i = kernel<T, N>[i];
  321. // Calculate hash input position, tiling where specified
  322. vector<T, N> hash_position = position_i + offset_i;
  323. for (std::size_t j = 0; j < N; ++j)
  324. {
  325. if (tiling[j])
  326. {
  327. hash_position[j] = std::fmod(hash_position[j], tiling[j]);
  328. if (hash_position[j] < T{0})
  329. hash_position[j] += tiling[j];
  330. }
  331. }
  332. // Calculate hash values for the hash position
  333. vector<hash::make_uint_t<T>, N> hash_i = hash(hash_position);
  334. // Convert hash values to pseudorandom fractional offset
  335. vector<T, N> offset_f = vector<T, N>(hash_i) * hash_scale;
  336. // Calculate displacement from input position to cell center
  337. vector<T, N> displacement = (offset_i + offset_f) - position_f;
  338. // Calculate square distance to the current cell center
  339. T sqr_distance = sqr_length(displacement);
  340. // Update F1 and F2 cells
  341. if (sqr_distance < f1_sqr_distance_center)
  342. {
  343. f2_sqr_distance_center = f1_sqr_distance_center;
  344. f2_displacement = f1_displacement;
  345. f2_hash = f1_hash;
  346. f1_sqr_distance_center = sqr_distance;
  347. f1_displacement = displacement;
  348. f1_hash = hash_i[0];
  349. }
  350. else if (sqr_distance < f2_sqr_distance_center)
  351. {
  352. f2_sqr_distance_center = sqr_distance;
  353. f2_displacement = displacement;
  354. f2_hash = hash_i[0];
  355. }
  356. }
  357. return
  358. {
  359. f1_sqr_distance_center,
  360. f1_displacement,
  361. f1_hash,
  362. f2_sqr_distance_center,
  363. f2_displacement,
  364. f2_hash,
  365. };
  366. }
  367. } // namespace voronoi
  368. } // namespace noise
  369. } // namespace math
  370. #endif // ANTKEEPER_MATH_NOISE_VORONOI_HPP