Antkeeper  0.0.1
voronoi.hpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 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 
20 #ifndef ANTKEEPER_MATH_NOISE_VORONOI_HPP
21 #define ANTKEEPER_MATH_NOISE_VORONOI_HPP
22 
23 #include <engine/math/vector.hpp>
25 #include <engine/math/hash/pcg.hpp>
26 #include <algorithm>
27 #include <array>
28 #include <cmath>
29 #include <tuple>
30 #include <limits>
31 #include <utility>
32 
33 namespace math {
34 namespace noise {
35 
37 namespace voronoi {
38 
46 template <std::size_t N>
47 constexpr std::size_t kernel_size = 4 << std::max<std::size_t>(0, (2 * (N - 1)));
48 
62 template <class T, std::size_t N, std::size_t... I>
63 [[nodiscard]] constexpr vector<T, N> kernel_offset(std::size_t i, std::index_sequence<I...>)
64 {
65  return {static_cast<T>((I ? (i / (2 << std::max<std::size_t>(0, 2 * I - 1))) : i) % 4)...};
66 }
67 
79 template <class T, std::size_t N, std::size_t... I>
80 [[nodiscard]] constexpr std::array<vector<T, N>, kernel_size<N>> generate_kernel(std::index_sequence<I...>)
81 {
82  return {kernel_offset<T, N>(I, std::make_index_sequence<N>{})...};
83 }
84 
93 template <class T, std::size_t N>
94 constexpr auto kernel = generate_kernel<T, N>(std::make_index_sequence<kernel_size<N>>{});
95 
109 template <class T, std::size_t N>
110 [[nodiscard]] std::tuple
111 <
112  // F1 square distance to center
113  T,
114 
115  // F1 position to center displacement
116  vector<T, N>,
117 
118  // F1 hash
120 >
122 (
123  const vector<T, N>& position,
124  T randomness = T{1},
125  const vector<T, N>& tiling = vector<T, N>::zero(),
126  vector<hash::make_uint_t<T>, N> (*hash)(const vector<T, N>&) = &hash::pcg<T, N>
127 )
128 {
129  // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
130  T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<hash::make_uint_t<T>>::max())) * randomness;
131 
132  // Get integer and fractional parts
133  vector<T, N> position_i = floor(position - T{1.5});
134  vector<T, N> position_f = position - position_i;
135 
136  // Find the F1 cell
137  T f1_sqr_distance = std::numeric_limits<T>::infinity();
138  vector<T, N> f1_displacement;
139  hash::make_uint_t<T> f1_hash;
140  for (std::size_t i = 0; i < kernel_size<N>; ++i)
141  {
142  // Get kernel offset for current cell
143  const vector<T, N>& offset_i = kernel<T, N>[i];
144 
145  // Calculate hash input position, tiling where specified
146  vector<T, N> hash_position = position_i + offset_i;
147  for (std::size_t j = 0; j < N; ++j)
148  {
149  if (tiling[j])
150  {
151  hash_position[j] = std::fmod(hash_position[j], tiling[j]);
152  if (hash_position[j] < T{0})
153  hash_position[j] += tiling[j];
154  }
155  }
156 
157  // Calculate hash values for the hash position
158  vector<hash::make_uint_t<T>, N> hash_i = hash(hash_position);
159 
160  // Convert hash values to pseudorandom fractional offset
161  vector<T, N> offset_f = vector<T, N>(hash_i) * hash_scale;
162 
163  // Calculate displacement from input position to cell center
164  vector<T, N> displacement = (offset_i + offset_f) - position_f;
165 
166  // Calculate square distance to the current cell center
167  T sqr_distance = sqr_length(displacement);
168 
169  // Update F1 cell
170  if (sqr_distance < f1_sqr_distance)
171  {
172  f1_sqr_distance = sqr_distance;
173  f1_displacement = displacement;
174  f1_hash = hash_i[0];
175  }
176  }
177 
178  return
179  {
180  f1_sqr_distance,
181  f1_displacement,
182  f1_hash
183  };
184 }
185 
199 template <class T, std::size_t N>
200 [[nodiscard]] std::tuple
201 <
202  // F1 square distance to center
203  T,
204 
205  // F1 position to center displacement
206  vector<T, N>,
207 
208  // F1 hash
209  hash::make_uint_t<T>,
210 
211  // Edge square distance
212  T
213 >
215 (
216  const vector<T, N>& position,
217  T randomness = T{1},
218  const vector<T, N>& tiling = vector<T, N>::zero(),
219  vector<hash::make_uint_t<T>, N> (*hash)(const vector<T, N>&) = &hash::pcg<T, N>
220 )
221 {
222  // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
223  T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<hash::make_uint_t<T>>::max())) * randomness;
224 
225  // Get integer and fractional parts
226  vector<T, N> position_i = floor(position - T{1.5});
227  vector<T, N> position_f = position - position_i;
228 
229  // Find F1 cell
230  T f1_sqr_distance_center = std::numeric_limits<T>::infinity();
231  vector<T, N> displacement_cache[kernel_size<N>];
232  std::size_t f1_i = 0;
233  hash::make_uint_t<T> f1_hash;
234  for (std::size_t i = 0; i < kernel_size<N>; ++i)
235  {
236  // Get kernel offset for current cell
237  const vector<T, N>& offset_i = kernel<T, N>[i];
238 
239  // Calculate hash input position, tiling where specified
240  vector<T, N> hash_position = position_i + offset_i;
241  for (std::size_t j = 0; j < N; ++j)
242  {
243  if (tiling[j])
244  {
245  hash_position[j] = std::fmod(hash_position[j], tiling[j]);
246  if (hash_position[j] < T{0})
247  hash_position[j] += tiling[j];
248  }
249  }
250 
251  // Calculate hash values for the hash position
252  vector<hash::make_uint_t<T>, N> hash_i = hash(hash_position);
253 
254  // Convert hash values to pseudorandom fractional offset
255  vector<T, N> offset_f = vector<T, N>(hash_i) * hash_scale;
256 
257  // Calculate and cache displacement from input position to cell center
258  displacement_cache[i] = (offset_i + offset_f) - position_f;
259 
260  // Calculate square distance to the current cell center
261  T sqr_distance = sqr_length(displacement_cache[i]);
262 
263  // Update F1 cell
264  if (sqr_distance < f1_sqr_distance_center)
265  {
266  f1_sqr_distance_center = sqr_distance;
267  f1_i = i;
268  f1_hash = hash_i[0];
269  }
270  }
271 
272  // Get displacement vector from input position to the F1 cell center
273  const vector<T, N>& f1_displacement = displacement_cache[f1_i];
274 
275  // Find distance to the closest edge
276  T edge_sqr_distance_edge = std::numeric_limits<T>::infinity();
277  for (std::size_t i = 0; i < kernel_size<N>; ++i)
278  {
279  // Skip F1 cell
280  if (i == f1_i)
281  continue;
282 
283  // Fetch cached displacement vector for current cell
284  const vector<T, N>& displacement = displacement_cache[i];
285 
286  // Find midpoint between the displacement vectors
287  const vector<T, N> midpoint = (f1_displacement + displacement) * T{0.5};
288 
289  // Calculate direction from the F1 cell to current cell
290  const vector<T, N> direction = normalize(displacement - f1_displacement);
291 
292  // Calculate square distance to the edge
293  const T sqr_distance = dot(midpoint, direction);
294 
295  // Update minimum edge distance if closer than the nearest edge
296  if (sqr_distance < edge_sqr_distance_edge)
297  edge_sqr_distance_edge = sqr_distance;
298  }
299 
300  return
301  {
302  f1_sqr_distance_center,
303  f1_displacement,
304  f1_hash,
305  edge_sqr_distance_edge
306  };
307 }
308 
322 template <class T, std::size_t N>
323 [[nodiscard]] std::tuple
324 <
325  // F1 square distance to center
326  T,
327 
328  // F1 position to center displacement
329  vector<T, N>,
330 
331  // F1 hash
332  hash::make_uint_t<T>,
333 
334  // F2 square distance to center
335  T,
336 
337  // F2 position to center displacement
338  vector<T, N>,
339 
340  // F2 hash
341  hash::make_uint_t<T>
342 >
344 (
345  const vector<T, N>& position,
346  T randomness = T{1},
347  const vector<T, N>& tiling = vector<T, N>::zero(),
348  vector<hash::make_uint_t<T>, N> (*hash)(const vector<T, N>&) = &hash::pcg<T, N>
349 )
350 {
351  // Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
352  T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<hash::make_uint_t<T>>::max())) * randomness;
353 
354  // Get integer and fractional parts
355  vector<T, N> position_i = floor(position - T{1.5});
356  vector<T, N> position_f = position - position_i;
357 
358  // Find the F1 and F2 cells
359  T f1_sqr_distance_center = std::numeric_limits<T>::infinity();
360  vector<T, N> f1_displacement = {0, 0};
361  hash::make_uint_t<T> f1_hash = 0;
362  T f2_sqr_distance_center = std::numeric_limits<T>::infinity();
363  vector<T, N> f2_displacement = {0, 0};
364  hash::make_uint_t<T> f2_hash = 0;
365  for (std::size_t i = 0; i < kernel_size<N>; ++i)
366  {
367  // Get kernel offset for current cell
368  const vector<T, N>& offset_i = kernel<T, N>[i];
369 
370  // Calculate hash input position, tiling where specified
371  vector<T, N> hash_position = position_i + offset_i;
372  for (std::size_t j = 0; j < N; ++j)
373  {
374  if (tiling[j])
375  {
376  hash_position[j] = std::fmod(hash_position[j], tiling[j]);
377  if (hash_position[j] < T{0})
378  hash_position[j] += tiling[j];
379  }
380  }
381 
382  // Calculate hash values for the hash position
383  vector<hash::make_uint_t<T>, N> hash_i = hash(hash_position);
384 
385  // Convert hash values to pseudorandom fractional offset
386  vector<T, N> offset_f = vector<T, N>(hash_i) * hash_scale;
387 
388  // Calculate displacement from input position to cell center
389  vector<T, N> displacement = (offset_i + offset_f) - position_f;
390 
391  // Calculate square distance to the current cell center
392  T sqr_distance = sqr_length(displacement);
393 
394  // Update F1 and F2 cells
395  if (sqr_distance < f1_sqr_distance_center)
396  {
397  f2_sqr_distance_center = f1_sqr_distance_center;
398  f2_displacement = f1_displacement;
399  f2_hash = f1_hash;
400 
401  f1_sqr_distance_center = sqr_distance;
402  f1_displacement = displacement;
403  f1_hash = hash_i[0];
404  }
405  else if (sqr_distance < f2_sqr_distance_center)
406  {
407  f2_sqr_distance_center = sqr_distance;
408  f2_displacement = displacement;
409  f2_hash = hash_i[0];
410  }
411  }
412 
413  return
414  {
415  f1_sqr_distance_center,
416  f1_displacement,
417  f1_hash,
418  f2_sqr_distance_center,
419  f2_displacement,
420  f2_hash,
421  };
422 }
423 
424 } // namespace voronoi
425 
426 } // namespace noise
427 } // namespace math
428 
429 #endif // ANTKEEPER_MATH_NOISE_VORONOI_HPP
Hash functions.
Definition: fnv1a.hpp:29
typename make_uint< T >::type make_uint_t
Helper type for make_uint.
Definition: make-uint.hpp:63
std::tuple< T, vector< T, N >, hash::make_uint_t< T >> f1(const vector< T, N > &position, T randomness=T{1}, const vector< T, N > &tiling=vector< T, N >::zero(), vector< hash::make_uint_t< T >, N >(*hash)(const vector< T, N > &)=&hash::pcg< T, N >)
Finds the Voronoi cell (F1) containing the input position.
Definition: voronoi.hpp:122
std::tuple< T, vector< T, N >, hash::make_uint_t< T >, T > f1_edge(const vector< T, N > &position, T randomness=T{1}, const vector< T, N > &tiling=vector< T, N >::zero(), vector< hash::make_uint_t< T >, N >(*hash)(const vector< T, N > &)=&hash::pcg< T, N >)
Finds the Voronoi cell (F1) containing the input position, along with the distance to the nearest edg...
Definition: voronoi.hpp:215
std::tuple< T, vector< T, N >, hash::make_uint_t< T >, T, vector< T, N >, hash::make_uint_t< T >> f1_f2(const vector< T, N > &position, T randomness=T{1}, const vector< T, N > &tiling=vector< T, N >::zero(), vector< hash::make_uint_t< T >, N >(*hash)(const vector< T, N > &)=&hash::pcg< T, N >)
Finds the Voronoi cell (F1) containing the input position, as well as the nearest neighboring cell (F...
Definition: voronoi.hpp:344
Mathematical functions and data types.
Definition: angles.hpp:26
constexpr T sqr_distance(const vector< T, N > &p0, const vector< T, N > &p1) noexcept
Calculates the square distance between two points.
Definition: vector.hpp:1514
constexpr vector< T, N > floor(const vector< T, N > &x)
Performs a element-wise floor operation.
Definition: vector.hpp:1184
quaternion< T > normalize(const quaternion< T > &q)
Normalizes a quaternion.
Definition: quaternion.hpp:679
constexpr T sqr_length(const quaternion< T > &q) noexcept
Calculates the square length of a quaternion.
Definition: quaternion.hpp:744
constexpr T dot(const quaternion< T > &a, const quaternion< T > &b) noexcept
Calculates the dot product of two quaternions.
Definition: quaternion.hpp:572
n-dimensional vector.
Definition: vector.hpp:44
static constexpr vector zero() noexcept
Returns a zero vector, where every element is equal to zero.
Definition: vector.hpp:306