Browse Source

Add Voronoi F1, F2, and edge functions

master
C. J. Howard 2 years ago
parent
commit
b5839b537b
6 changed files with 548 additions and 12 deletions
  1. +25
    -5
      src/game/load.cpp
  2. +5
    -4
      src/math/noise/fbm.hpp
  3. +1
    -0
      src/math/noise/noise.hpp
  4. +3
    -2
      src/math/noise/simplex.hpp
  5. +398
    -0
      src/math/noise/voronoi.hpp
  6. +116
    -1
      src/math/vector.hpp

+ 25
- 5
src/game/load.cpp View File

@ -98,14 +98,31 @@ void biome(game::context& ctx, const std::filesystem::path& path)
}; };
//float n = math::noise::simplex<float, 2>(position, &math::noise::hash::pcg3d_1); //float n = math::noise::simplex<float, 2>(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<float, std::uint32_t>(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<unsigned char>((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<float, std::uint32_t>(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<unsigned char>(std::min(255.0f, f1_distance * 255.0f));
//pixel = static_cast<unsigned char>(id % 255);
} }
); );
stbi_flip_vertically_on_write(1); 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 try
@ -192,6 +209,7 @@ void biome(game::context& ctx, const std::filesystem::path& path)
float2 position = float2{x, z} * frequency; float2 position = float2{x, z} * frequency;
/*
float n = math::noise::fbm float n = math::noise::fbm
( (
position, position,
@ -201,8 +219,10 @@ void biome(game::context& ctx, const std::filesystem::path& path)
noise, noise,
hash hash
); );
return 2.0f * n;
*/
//float n = math::noise::voronoi::f1<float, std::uint32_t>(position, 1.0f, &math::noise::hash::pcg3d_3)[0];
float n = 0.0f;
return 10.0f * n;
} }
); );

+ 5
- 4
src/math/noise/fbm.hpp View File

@ -21,7 +21,7 @@
#define ANTKEEPER_MATH_NOISE_FBM_HPP #define ANTKEEPER_MATH_NOISE_FBM_HPP
#include "math/vector.hpp" #include "math/vector.hpp"
#include <functional>
#include <cstdint>
namespace math { namespace math {
namespace noise { namespace noise {
@ -31,6 +31,7 @@ namespace noise {
* *
* @tparam T Real type. * @tparam T Real type.
* @tparam N Number of dimensions. * @tparam N Number of dimensions.
* @tparam U Hash function return type.
* *
* @param x n-dimensional input value. * @param x n-dimensional input value.
* @param octaves Number of octaves. * @param octaves Number of octaves.
@ -40,15 +41,15 @@ namespace noise {
* @param hash Hash function. * @param hash Hash function.
* *
*/ */
template <class T, std::size_t N>
template <class T, std::size_t N, class U>
T fbm T fbm
( (
vector<T, N> x, vector<T, N> x,
std::size_t octaves, std::size_t octaves,
T lacunarity, T lacunarity,
T gain, T gain,
T (*noise)(const vector<T, N>&, std::uint32_t (*)(const vector<T, N>&)),
std::uint32_t (*hash)(const vector<T, N>&)
T (*noise)(const vector<T, N>&, U (*)(const vector<T, N>&)),
U (*hash)(const vector<T, N>&)
) )
{ {
T amplitude{1}; T amplitude{1};

+ 1
- 0
src/math/noise/noise.hpp View File

@ -30,5 +30,6 @@ namespace noise {}
#include "math/noise/fbm.hpp" #include "math/noise/fbm.hpp"
#include "math/noise/hash.hpp" #include "math/noise/hash.hpp"
#include "math/noise/simplex.hpp" #include "math/noise/simplex.hpp"
#include "math/noise/voronoi.hpp"
#endif // ANTKEEPER_MATH_NOISE_HPP #endif // ANTKEEPER_MATH_NOISE_HPP

+ 3
- 2
src/math/noise/simplex.hpp View File

@ -96,6 +96,7 @@ constexpr auto simplex_edges = make_simplex_edges(std::make_index_sequence
* *
* @tparam T Real type. * @tparam T Real type.
* @tparam N Number of dimensions. * @tparam N Number of dimensions.
* @tparam U Hash function return type.
* *
* @param x Input vector. * @param x Input vector.
* @param hash Hash function. * @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://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 * @see https://math.stackexchange.com/questions/474638/radius-and-amplitude-of-kernel-for-simplex-noise/1901116
*/ */
template <class T, std::size_t N>
T simplex(const vector<T, N>& x, std::uint32_t (*hash)(const vector<T, N>&))
template <class T, std::size_t N, class U>
T simplex(const vector<T, N>& x, U (*hash)(const vector<T, N>&))
{ {
// Skewing (F) and unskewing (G) factors // Skewing (F) and unskewing (G) factors
static const T f = (std::sqrt(static_cast<T>(N + 1)) - T{1}) / static_cast<T>(N); static const T f = (std::sqrt(static_cast<T>(N + 1)) - T{1}) / static_cast<T>(N);

+ 398
- 0
src/math/noise/voronoi.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_MATH_NOISE_VORONOI_HPP
#define ANTKEEPER_MATH_NOISE_VORONOI_HPP
#include "math/vector.hpp"
#include <algorithm>
#include <cmath>
#include <tuple>
#include <limits>
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 <class T>
constexpr vector<T, 2> 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 <class T>
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 <class T, class U>
std::tuple
<
// F1 square distance to center
T,
// F1 position to center displacement
vector<T, 2>,
// F1 hash
U
>
f1
(
const vector<T, 2>& position,
T randomness,
const vector<T, 2>& tiling,
vector<U, 3> (*hash)(const vector<T, 2>&)
)
{
// Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<U>::max())) * randomness;
// Get integer and fractional parts
vector<T, 2> position_i = math::floor(position - T{1.5});
vector<T, 2> position_f = position - position_i;
// Find the F1 cell
T f1_sqr_distance = f1_max_sqr_distance<T>;
vector<T, 2> f1_displacement;
U f1_hash;
for (std::size_t i = 0; i < f1_kernel_size; ++i)
{
// Get kernel offset for current cell
const vector<T, 2>& offset_i = f1_kernel<T>[i];
// Calculate hash input position, tiling where specified
vector<T, 2> 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<U, 2> hash_i = vector<U, 2>(hash(hash_position));
// Convert hash values to pseudorandom fractional offset
vector<T, 2> offset_f = vector<T, 2>(hash_i) * hash_scale;
// Calculate displacement from input position to cell center
vector<T, 2> 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 <class T, class U>
std::tuple
<
// F1 square distance to center
T,
// F1 position to center displacement
vector<T, 2>,
// F1 hash
U,
// Edge square distance
T
>
f1_edge
(
const vector<T, 2>& position,
T randomness,
const vector<T, 2>& tiling,
vector<U, 3> (*hash)(const vector<T, 2>&)
)
{
// Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<U>::max())) * randomness;
// Get integer and fractional parts
vector<T, 2> position_i = math::floor(position - T{1.5});
vector<T, 2> position_f = position - position_i;
// Find F1 cell
T f1_sqr_distance_center = f1_max_sqr_distance<T>;
vector<T, 2> displacement_cache[4][4];
int f1_i = 0;
int f1_j = 0;
U f1_hash;
vector<T, 2> offset_i;
for (int i = 0; i < 4; ++i)
{
offset_i[0] = static_cast<T>(i);
for (int j = 0; j < 4; ++j)
{
offset_i[1] = static_cast<T>(j);
// Calculate hash input position, tiling where specified
vector<T, 2> 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<U, 2> hash_i = vector<U, 2>(hash(hash_position));
// Convert hash values to pseudorandom fractional offset
vector<T, 2> offset_f = vector<T, 2>(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<T, 2>& f1_displacement = displacement_cache[f1_i][f1_j];
// Find distance to the closest edge
T edge_sqr_distance_edge = f1_max_sqr_distance<T>;
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<T, 2>& displacement = displacement_cache[i][j];
// Find midpoint between the displacement vectors
const vector<T, 2> midpoint = (f1_displacement + displacement) * T{0.5};
// Calculate direction from the F1 cell to current cell
const vector<T, 2> 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 <class T, class U>
std::tuple
<
// F1 square distance to center
T,
// F1 position to center displacement
vector<T, 2>,
// F1 hash
U,
// F2 square distance to center
T,
// F2 position to center displacement
vector<T, 2>,
// F2 hash
U
>
f1_f2
(
const vector<T, 2>& position,
T randomness,
const vector<T, 2>& tiling,
vector<U, 3> (*hash)(const vector<T, 2>&)
)
{
// Calculate factor which scales hash value onto `[0, 1]`, modulated by desired randomness
T hash_scale = (T{1} / static_cast<T>(std::numeric_limits<U>::max())) * randomness;
// Get integer and fractional parts
vector<T, 2> position_i = math::floor(position - T{1.5});
vector<T, 2> position_f = position - position_i;
// Find the F1 and F2 cells
T f1_sqr_distance_center = f1_max_sqr_distance<T>;
vector<T, 2> f1_displacement = {0, 0};
U f1_hash = 0;
T f2_sqr_distance_center = f1_max_sqr_distance<T>;
vector<T, 2> f2_displacement = {0, 0};
U f2_hash = 0;
vector<T, 2> offset_i;
for (int i = 0; i < 4; ++i)
{
offset_i[0] = static_cast<T>(i);
for (int j = 0; j < 4; ++j)
{
offset_i[1] = static_cast<T>(j);
// Calculate hash input position, tiling where specified
vector<T, 2> 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<U, 2> hash_i = vector<U, 2>(hash(hash_position));
// Convert hash values to pseudorandom fractional offset
vector<T, 2> offset_f = vector<T, 2>(hash_i) * hash_scale;
// Calculate displacement from input position to cell center
vector<T, 2> 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

+ 116
- 1
src/math/vector.hpp View File

@ -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. * @tparam U Target element type.
* *
@ -512,6 +512,17 @@ constexpr vector less_than(const vector& x, const vector& y
template <class T, std::size_t N> template <class T, std::size_t N>
constexpr vector<bool, N> less_than_equal(const vector<T, N>& x, const vector<T, N>& y); constexpr vector<bool, N> less_than_equal(const vector<T, N>& x, const vector<T, N>& 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 <class T, std::size_t N>
constexpr vector<T, N> max(const vector<T, N>& x, const vector<T, N>& y);
/** /**
* Returns the value of the greatest element in a vector. * Returns the value of the greatest element in a vector.
* *
@ -522,6 +533,17 @@ constexpr vector less_than_equal(const vector& x, const vector
template <class T, std::size_t N> template <class T, std::size_t N>
constexpr T max(const vector<T, N>& x); constexpr T max(const vector<T, N>& 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 <class T, std::size_t N>
constexpr vector<T, N> min(const vector<T, N>& x, const vector<T, N>& y);
/** /**
* Returns the value of the smallest element in a vector. * Returns the value of the smallest element in a vector.
* *
@ -597,6 +619,21 @@ constexpr vector not(const vector& x);
template <class T, std::size_t N> template <class T, std::size_t N>
constexpr vector<bool, N> not_equal(const vector<T, N>& x, const vector<T, N>& y); constexpr vector<bool, N> not_equal(const vector<T, N>& x, const vector<T, N>& 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 <class T, std::size_t N>
vector<T, N> pow(const vector<T, N>& x, const vector<T, N>& y);
template <class T, std::size_t N>
vector<T, N> pow(const vector<T, N>& x, T y);
/// @}
/** /**
* Resizes a vector. Any new elements will be set to `0`. * Resizes a vector. Any new elements will be set to `0`.
* *
@ -627,6 +664,16 @@ constexpr vector round(const vector& x);
template <class T, std::size_t N> template <class T, std::size_t N>
constexpr vector<T, N> sign(const vector<T, N>& x); constexpr vector<T, N> sign(const vector<T, N>& x);
/**
* Takes the square root of each element.
*
* @param x Input vector
*
* @return Square roots of the input vector elements.
*/
template <class T, std::size_t N>
vector<T, N> sqrt(const vector<T, N>& x);
/** /**
* Subtracts a value by another value. * 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<N>{}); return less_than_equal(x, y, std::make_index_sequence<N>{});
} }
/// @private
template <class T, std::size_t N, std::size_t... I>
constexpr inline vector<T, N> max(const vector<T, N>& x, const vector<T, N>& y, std::index_sequence<I...>)
{
return {std::max<T>(x[I], y[I])...};
}
template <class T, std::size_t N>
constexpr vector<T, N> max(const vector<T, N>& x, const vector<T, N>& y)
{
return max(x, y, std::make_index_sequence<N>{});
}
template <class T, std::size_t N> template <class T, std::size_t N>
constexpr inline T max(const vector<T, N>& x) constexpr inline T max(const vector<T, N>& x)
{ {
return *std::max_element(x.elements, x.elements + N); return *std::max_element(x.elements, x.elements + N);
} }
/// @private
template <class T, std::size_t N, std::size_t... I>
constexpr inline vector<T, N> min(const vector<T, N>& x, const vector<T, N>& y, std::index_sequence<I...>)
{
return {std::min<T>(x[I], y[I])...};
}
template <class T, std::size_t N>
constexpr vector<T, N> min(const vector<T, N>& x, const vector<T, N>& y)
{
return min(x, y, std::make_index_sequence<N>{});
}
template <class T, std::size_t N> template <class T, std::size_t N>
constexpr inline T min(const vector<T, N>& x) constexpr inline T min(const vector<T, N>& x)
{ {
@ -1116,6 +1189,34 @@ constexpr inline vector not_equal(const vector& x, const vector
return not_equal(x, y, std::make_index_sequence<N>{}); return not_equal(x, y, std::make_index_sequence<N>{});
} }
/// @private
template <class T, std::size_t N, std::size_t... I>
inline vector<T, N> pow(const vector<T, N>& x, const vector<T, N>& y, std::index_sequence<I...>)
{
return {std::pow(x[I], y[I])...};
}
template <class T, std::size_t N>
inline vector<T, N> pow(const vector<T, N>& x, const vector<T, N>& y)
{
static_assert(std::is_floating_point<T>::value);
return pow(x, y, std::make_index_sequence<N>{});
}
/// @private
template <class T, std::size_t N, std::size_t... I>
inline vector<T, N> pow(const vector<T, N>& x, T y, std::index_sequence<I...>)
{
return {std::pow(x[I], y)...};
}
template <class T, std::size_t N>
inline vector<T, N> pow(const vector<T, N>& x, T y)
{
static_assert(std::is_floating_point<T>::value);
return pow(x, y, std::make_index_sequence<N>{});
}
/// @private /// @private
template <std::size_t M, class T, std::size_t N, std::size_t... I> template <std::size_t M, class T, std::size_t N, std::size_t... I>
constexpr inline vector<T, M> resize(const vector<T, N>& x, std::index_sequence<I...>) constexpr inline vector<T, M> resize(const vector<T, N>& x, std::index_sequence<I...>)
@ -1157,6 +1258,20 @@ constexpr inline vector sign(const vector& x)
return sign(x, std::make_index_sequence<N>{}); return sign(x, std::make_index_sequence<N>{});
} }
/// @private
template <class T, std::size_t N, std::size_t... I>
inline vector<T, N> sqrt(const vector<T, N>& x, std::index_sequence<I...>)
{
return {std::sqrt(x[I])...};
}
template <class T, std::size_t N>
inline vector<T, N> sqrt(const vector<T, N>& x, const vector<T, N>& y)
{
static_assert(std::is_floating_point<T>::value);
return pow(x, std::make_index_sequence<N>{});
}
/// @private /// @private
template <class T, std::size_t N, std::size_t... I> template <class T, std::size_t N, std::size_t... I>
constexpr inline vector<T, N> sub(const vector<T, N>& x, const vector<T, N>& y, std::index_sequence<I...>) constexpr inline vector<T, N> sub(const vector<T, N>& x, const vector<T, N>& y, std::index_sequence<I...>)

Loading…
Cancel
Save