libcruft-util/coord/ops.hpp
Danny Robson fdaa5e1392 assert: split CHECK_LIMIT into INCLUSIVE and INDEX
LIMIT hid an off-by-one bug when tests used end iterators. We rename the
assertion to uncover all uses of the flawed implementation, and split it
into an identical assertion, and one intended to protect against
iterator ends.
2020-09-24 08:03:41 +10:00

1622 lines
45 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2012-2019 Danny Robson <danny@nerdcruft.net>
*/
#pragma once
#include "fwd.hpp"
#include "traits.hpp"
#include "../array/varray.hpp"
// we specifically rely on vector<bool> to compute a few logical operations
#include "../vector.hpp"
#include "../tuple/value.hpp"
#include "../debug/assert.hpp"
#include "../maths.hpp"
#include "../types/bits.hpp"
#include <cruft/util/preprocessor.hpp>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <iterator>
#include <functional>
namespace cruft {
/// returns the data at a templated index in a coordinate.
///
/// specifically required for structured bindings support.
///
/// \tparam I index of the requested data
/// \tparam S dimensionality of the coordinate
/// \tparam T underlying data type of the coordinate
/// \tparam K coordinate data type to operate on
template <
std::size_t I,
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
const auto&
get (const K &k)
{
static_assert (I < K::elements);
return k[I];
};
/// returns the data at a templated index in a coordinate.
///
/// specifically required for structured bindings support.
///
/// \tparam I index of the requested data
/// \tparam S dimensionality of the coordinate
/// \tparam T underlying data type of the coordinate
/// \tparam K coordinate data type to operate on
template <
std::size_t I,
typename K,
typename = std::enable_if_t<
is_coord_v<K> && I < K::elements, void
>
>
auto &
get (K &k)
{
static_assert (I < K::elements);
return k[I];
};
///////////////////////////////////////////////////////////////////////////
// a templated functor that exposes arithmetic and assignment maths
// functions for vector-vector or vector-scalar operations.
//
// we implement the operations this way because it (somewhat) simplifies
// ambiguity resolution in the various operators we need to provide.
// eg, operator+(vec,vec) vs operator+(vec,int).
//
// it used to be directly implemented with a series of templated free
// functions when we could restrict the arguments more easily with quite
// specific template template parameters. but the introduction of
// coordinate types that do not expose size or type information as template
// parameters we can't rely on this mechanism anymore.
template <typename, typename, typename=void>
struct assignment {};
//-------------------------------------------------------------------------
template <typename CoordA, typename CoordB>
struct assignment<
CoordA,
CoordB,
std::enable_if_t<
is_coord_v<CoordA> &&
is_coord_v<CoordB> &&
arity_v<CoordA> == arity_v<CoordB> &&
std::is_same_v<
typename CoordA::value_type,
std::common_type_t<
typename CoordA::value_type,
typename CoordB::value_type
>
>
,
void
>
> {
template <typename OperationT>
static constexpr CoordA&
eval (OperationT &&op, CoordA &a, const CoordB &b)
{
for (std::size_t i = 0; i < CoordA::elements; ++i)
a[i] = op (a[i], b[i]);
return a;
}
};
//-------------------------------------------------------------------------
// vector-scalar operations
template <
typename CoordT,
typename ScalarT
>
struct assignment<
CoordT,
ScalarT,
std::enable_if_t<
is_coord_v<CoordT> &&
!is_coord_v<ScalarT> &&
has_scalar_op_v<CoordT> &&
std::is_same_v<
typename CoordT::value_type,
std::common_type_t<
typename CoordT::value_type,
ScalarT
>
>
,
void
>
> {
// we allow scalar types which can be naturally promoted to the vector's
// value_type
template <typename OperationT>
static constexpr CoordT&
eval (OperationT &&op, CoordT &coord, const ScalarT scalar)
{
for (size_t i = 0; i < CoordT::elements; ++i)
coord[i] = op (coord[i], scalar);
return coord;
}
};
///////////////////////////////////////////////////////////////////////////
/// create a coord from supplied arguments, optionally specifying the
/// underlying type.
///
/// much like experimental::make_array we use a void type to signal we
/// need to deduce the underlying type.
#define MAKE_COORD(KLASS) \
template < \
typename _T = void, \
typename ...Args \
> \
constexpr auto \
make_##KLASS (Args &&...args) \
{ \
using T = std::conditional_t< \
std::is_void_v<_T>, \
std::common_type_t<Args...>, \
_T \
>; \
\
return KLASS<sizeof...(Args),T> { \
std::forward<Args> (args)... \
}; \
}
MAKE_COORD(extent)
MAKE_COORD(point)
MAKE_COORD(vector)
#undef MAKE_COORD
template <
template <std::size_t,typename> class K,
typename ...Args
>
constexpr auto
make_coord (Args &&...args)
{
using T = std::common_type_t<Args...>;
return K<sizeof...(Args),T> { std::forward<Args> (args)... };
}
///////////////////////////////////////////////////////////////////////////
template <typename ValueA, typename ValueB, typename=void>
struct arithmetic {};
//-------------------------------------------------------------------------
template <typename CoordA, typename CoordB>
struct arithmetic<
CoordA,
CoordB,
std::enable_if_t<
is_coord_v<CoordA> &&
is_coord_v<CoordB> &&
arity_v<CoordA> == arity_v<CoordB> &&
has_result_v<CoordA,CoordB>
,
void
>
> {
template <typename OperationT>
static constexpr auto
eval (OperationT &&op, const CoordA &a, const CoordB &b)
{
using common_t = std::common_type_t<
typename CoordA::value_type,
typename CoordB::value_type
>;
revalue_t<result_t<CoordA,CoordB>,common_t> out {};
for (size_t i = 0; i < CoordA::elements; ++i)
out[i] = op (a[i], b[i]);
return out;
}
};
//-------------------------------------------------------------------------
template <typename CoordT, typename ScalarT>
struct arithmetic<
CoordT,
ScalarT,
std::enable_if_t<
is_coord_v<CoordT> && std::is_arithmetic_v<ScalarT> && has_scalar_op_v<CoordT>,
void
>
> {
template <typename OperationT>
static constexpr auto
eval (OperationT &&op, const CoordT &coord, const ScalarT &scalar)
{
using common_t = std::common_type_t<typename CoordT::value_type, ScalarT>;
revalue_t<CoordT,common_t> out {};
for (size_t i = 0; i < CoordT::elements; ++i)
out[i] = op (coord[i], scalar);
return out;
}
};
//-------------------------------------------------------------------------
template <typename ScalarT, typename CoordT>
struct arithmetic<
ScalarT,
CoordT,
std::enable_if_t<
is_coord_v<CoordT> && std::is_arithmetic_v<ScalarT> && has_scalar_op_v<CoordT>,
void
>
> {
template <typename OperationT>
static constexpr auto
eval (OperationT &&op, const ScalarT &scalar, const CoordT &coord)
{
using common_t = std::common_type_t<typename CoordT::value_type, ScalarT>;
revalue_t<CoordT,common_t> out {};
for (size_t i = 0; i < CoordT::elements; ++i)
out[i] = op (scalar, coord[i]);
return out;
}
};
///////////////////////////////////////////////////////////////////////////
template <
typename A,
typename B,
typename = std::enable_if_t<
(is_coord_v<std::decay_t<A>> || is_coord_v<std::decay_t<B>>) &&
(is_coord_v<std::decay_t<A>> || std::is_arithmetic_v<std::decay_t<A>>) &&
(is_coord_v<std::decay_t<B>> || std::is_arithmetic_v<std::decay_t<B>>)
>
>
constexpr auto
operator+ (A &&a, B &&b)
{
return arithmetic<std::decay_t<A>, std::decay_t<B>>::template eval (std::plus{}, a, b);
}
template <
typename A,
typename B,
typename = std::enable_if_t<
(is_coord_v<std::decay_t<A>> || is_coord_v<std::decay_t<B>>) &&
(is_coord_v<std::decay_t<A>> || std::is_arithmetic_v<std::decay_t<A>>) &&
(is_coord_v<std::decay_t<B>> || std::is_arithmetic_v<std::decay_t<B>>)
>
>
constexpr auto
operator- (A &&a, B &&b)
{
return arithmetic<std::decay_t<A>, std::decay_t<B>>::template eval (std::minus{}, a, b);
}
template <
typename A,
typename B,
typename = std::enable_if_t<
(is_coord_v<std::decay_t<A>> || is_coord_v<std::decay_t<B>>) &&
(is_coord_v<std::decay_t<A>> || std::is_arithmetic_v<std::decay_t<A>>) &&
(is_coord_v<std::decay_t<B>> || std::is_arithmetic_v<std::decay_t<B>>)
>
>
constexpr auto
operator* (A &&a, B &&b)
{
return arithmetic<
std::decay_t<A>,
std::decay_t<B>
>::template eval (std::multiplies{}, a, b);
}
template <
typename A,
typename B,
typename = std::enable_if_t<
(is_coord_v<std::decay_t<A>> || is_coord_v<std::decay_t<B>>) &&
(is_coord_v<std::decay_t<A>> || std::is_arithmetic_v<std::decay_t<A>>) &&
(is_coord_v<std::decay_t<B>> || std::is_arithmetic_v<std::decay_t<B>>)
>
>
constexpr auto
operator/ (A &&a, B &&b)
{
return arithmetic<std::decay_t<A>, std::decay_t<B>>::template eval (std::divides{}, a, b);
}
//-------------------------------------------------------------------------
template <
typename A,
typename B,
typename = std::enable_if_t<
(is_coord_v<std::decay_t<A>> || is_coord_v<std::decay_t<B>>) &&
(is_coord_v<std::decay_t<A>> || std::is_arithmetic_v<std::decay_t<A>>) &&
(is_coord_v<std::decay_t<B>> || std::is_arithmetic_v<std::decay_t<B>>)
>
>
constexpr auto
operator += (A &&a, B &&b)
{
return assignment<
std::decay_t<A>,
std::decay_t<B>
>::eval (std::plus{}, a, b);
}
template <
typename A,
typename B,
typename = std::enable_if_t<
(is_coord_v<std::decay_t<A>> || is_coord_v<std::decay_t<B>>) &&
(is_coord_v<std::decay_t<A>> || std::is_arithmetic_v<std::decay_t<A>>) &&
(is_coord_v<std::decay_t<B>> || std::is_arithmetic_v<std::decay_t<B>>)
>
>
constexpr auto
operator -= (A &&a, B &&b)
{
return assignment<
std::decay_t<A>,
std::decay_t<B>
>::eval (std::minus{}, a, b);
}
template <
typename A,
typename B,
typename = std::enable_if_t<
(is_coord_v<std::decay_t<A>> || is_coord_v<std::decay_t<B>>) &&
(is_coord_v<std::decay_t<A>> || std::is_arithmetic_v<std::decay_t<A>>) &&
(is_coord_v<std::decay_t<B>> || std::is_arithmetic_v<std::decay_t<B>>)
>
>
constexpr auto
operator *= (A &&a, B &&b)
{
return assignment<
std::decay_t<A>,
std::decay_t<B>
>::eval (std::multiplies{}, a, b);
}
template <
typename A,
typename B,
typename = std::enable_if_t<
(is_coord_v<std::decay_t<A>> || is_coord_v<std::decay_t<B>>) &&
(is_coord_v<std::decay_t<A>> || std::is_arithmetic_v<std::decay_t<A>>) &&
(is_coord_v<std::decay_t<B>> || std::is_arithmetic_v<std::decay_t<B>>)
>
>
constexpr auto
operator /= (A &&a, B &&b)
{
return assignment<
std::decay_t<A>,
std::decay_t<B>
>::eval (std::divides{}, a, b);
}
///////////////////////////////////////////////////////////////////////////
// unary operators
#define UNARY_OP(OP) \
template < \
typename K, \
typename = std::enable_if_t< \
is_coord_v<K>, void \
> \
> \
constexpr \
auto \
operator OP (K k) \
{ \
using value_type = decltype( \
OP std::declval<typename K::value_type> () \
); \
\
revalue_t<K,value_type> out {}; \
\
for (std::size_t i = 0; i < K::elements; ++i) \
out[i] = OP k[i]; \
\
return out; \
}
UNARY_OP(!)
UNARY_OP(~)
UNARY_OP(+)
UNARY_OP(-)
#undef UNARY_OP
///////////////////////////////////////////////////////////////////////////
namespace detail {
/// invoke a function elementwise to the arguments elementwise.
///
/// \tparam ArgsT a tuple containing the (coord) arguments for the func
template <
typename RetT,
typename FuncT,
typename ArgsT,
std::size_t ...Indices
>
constexpr auto
apply (const std::index_sequence<Indices...>,
FuncT &&func,
ArgsT args) noexcept
{
using part_t = std::tuple_element_t<0,ArgsT>;
using value_t = typename part_t::value_type;
return RetT {
std::apply (
func,
::cruft::tuple::value::map (
static_cast<
const value_t& (&)(const part_t&)
> (
get<Indices,RetT>
), args
)
)...
};
}
}
//-------------------------------------------------------------------------
// invokes a function elementwise using elementwise parameters from the
// supplied arguments.
//
// equivalent to this pseduocode:
// for (int i: indices (ReturnT))
// res[i] = func (args[i]...);
// return res;
//
// forwards the arguments as a tuple to a helper function that has access
// to indices as a template parameter.
template <
typename ReturnT,
typename FuncT,
typename ...ArgT,
typename = std::enable_if_t<
// return type and arguments must be coordinates
(is_coord_v<ReturnT> && ... && is_coord_v<std::decay_t<ArgT>>) &&
// all types must be the same arity
((ReturnT::elements == std::decay_t<ArgT>::elements) && ...) &&
// all the arguments must be the same type
(std::is_same_v<
std::tuple_element_t<0,std::tuple<std::decay_t<ArgT>...>>,
std::decay_t<ArgT>
> && ...),
void
>,
typename Indices = std::make_index_sequence<ReturnT::elements>
>
constexpr auto
invoke (FuncT &&func, ArgT &&...args) noexcept
{
return detail::apply<ReturnT> (
Indices{},
std::forward<FuncT> (func),
std::tuple (std::forward<ArgT> (args)...)
);
}
///////////////////////////////////////////////////////////////////////////
// logic operators
namespace detail {
template <
typename K,
typename FuncT,
typename = std::enable_if_t<
is_coord_v<K>, void
>,
std::size_t ...Indices
>
constexpr auto
compare (FuncT &&func, std::index_sequence<Indices...>, const K a, const K b)
{
return vector<K::elements,bool> {
std::invoke (func, a[Indices], b[Indices])...
};
}
}
//-------------------------------------------------------------------------
template <
typename K,
typename FuncT,
typename = std::enable_if_t<
is_coord_v<K>, void
>,
typename Indices = std::make_index_sequence<K::elements>
>
constexpr auto
compare (const K a, const K b, FuncT &&func)
{
return detail::compare (std::forward<FuncT> (func), Indices{}, a, b);
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
compare (const K a, const K b)
{
return compare (a, b, std::equal_to<typename K::value_type> {});
}
/// elementwise equality operator
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr bool
operator== (const K a, const K b)
{
return all (compare (a, b, std::equal_to<typename K::value_type> {}));
}
///------------------------------------------------------------------------
/// elementwise inquality operator
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr bool
operator!= (const K a, const K b)
{
return any (compare (a, b, std::not_equal_to<typename K::value_type> {}));
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr
bool
almost_zero (const K &k)
{
return std::all_of (
std::cbegin (k),
std::cend (k),
[] (auto t) { return almost_zero (t); }
);
}
///////////////////////////////////////////////////////////////////////////
// special operators
/// point-point subtraction giving a vector difference
template <
std::size_t S,
typename T,
typename U
>
constexpr
vector<S,std::common_type_t<T,U>>
operator- (point<S,T> a, point<S,U> b)
{
vector<S,std::common_type_t<T,U>> out {};
for (std::size_t i = 0; i < S; ++i)
out[i] = a[i] - b[i];
return out;
}
//-------------------------------------------------------------------------
template <
std::size_t S,
typename T,
typename U,
typename = std::enable_if_t<
std::is_arithmetic<T>::value && std::is_arithmetic<U>::value,
void
>
>
constexpr
vector<S,std::common_type_t<T,U>>
operator- (U u, point<S,T> p)
{
return point<S,U> {u} - p;
}
///////////////////////////////////////////////////////////////////////////
template <std::size_t S, typename T>
constexpr T
dot (
cruft::varray<S,T> const &a,
cruft::varray<S,T> const &b
) {
T sum = 0;
for (std::size_t i = 0; i < S; ++i)
sum += a[i] * b[i];
return sum;
}
template <
std::size_t S,
typename T,
typename K,
typename = std::enable_if_t<
is_coord_v<K> && std::is_same_v<typename K::value_type, T> && K::elements == S,
void
>
>
constexpr
T
dot (const T (&a)[S], K k)
{
return dot (a, k.data);
}
//-------------------------------------------------------------------------
template <
typename A,
typename B,
typename = std::enable_if_t<
is_coord_v<A> && is_coord_v<B>, void
>
>
constexpr auto
dot (A a, B b)
{
return dot (varray (a.data), varray (b.data));
}
//-------------------------------------------------------------------------
template <
typename K,
typename T,
typename = std::enable_if_t<
is_coord_v<K> && std::is_same_v<T,typename K::value_type>, void
>
>
constexpr auto
dot (K a, const T (&b)[K::elements])
{
return dot (a.data, b);
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
dot (const typename K::value_type (&a)[K::elements], K b)
{
return dot (a, b.data);
}
///////////////////////////////////////////////////////////////////////////
template <
typename K,
typename = std::enable_if_t<has_norm_v<K>,void>
>
constexpr
auto
norm2 (const K &k)
{
return dot (k, k);
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
has_norm_v<K>,
void
>
>
constexpr
auto
norm (const K &k)
{
return std::sqrt (norm2 (k));
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
has_norm_v<K>,
void
>
>
constexpr
auto
normalised (const K &k)
{
CHECK_NEZ (norm (k));
return k / norm (k);
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
has_norm_v<K>,
void
>
>
constexpr
bool
is_normalised (const K &k)
{
using value_type = typename K::value_type;
constexpr auto hi = value_type (1.00001);
constexpr auto lo = value_type (0.99999);
const auto n2 = norm2 (k);
return n2 < hi && n2 > lo;
}
///////////////////////////////////////////////////////////////////////////
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr
K
abs (K k)
{
for (auto &v: k)
v = std::abs (v);
return k;
}
///////////////////////////////////////////////////////////////////////////
template <
typename BaseT,
typename = std::enable_if_t<is_coord_v<BaseT>>
>
constexpr
auto
pow (BaseT k, float p)
{
for (auto &v: k)
v = std::pow (v, p);
return k;
}
///////////////////////////////////////////////////////////////////////////
// root of sum of squares
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr typename K::value_type
hypot (K k)
{
return std::sqrt (sum (k * k));
}
///////////////////////////////////////////////////////////////////////////
template <
typename K,
typename T,
typename = std::enable_if_t<
is_coord_v<K> && std::is_same_v<T, typename K::value_type>, void
>
>
constexpr auto
mod (K k, T t)
{
std::transform (
std::cbegin (k),
std::cend (k),
std::begin (k),
[t] (auto v) { return mod (v, t);
});
return k;
}
//-------------------------------------------------------------------------
template <
typename A,
typename B,
typename = std::enable_if_t<
is_coord_v<std::decay_t<A>> && is_coord_v<std::decay_t<B>>
>
>
constexpr auto
mod (A &&a, B &&b)
{
return arithmetic<
std::decay_t<A>,
std::decay_t<B>
>::template eval (
std::modulus{},
std::forward<A> (a),
std::forward<B> (b)
);
}
///////////////////////////////////////////////////////////////////////////
// trigonometric functions
template <
typename K,
typename = std::enable_if_t<is_coord_v<K>,void>
>
constexpr auto
sin (K k)
{
std::transform (
std::cbegin (k),
std::cend (k),
std::begin (k),
[] (auto v) { return std::sin (v); }
);
return k;
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<is_coord_v<K>,void>
>
constexpr auto
cos (K k)
{
std::transform (
std::cbegin (k),
std::cend (k),
std::begin (k),
[] (auto v) { return std::cos (v); }
);
return k;
}
///////////////////////////////////////////////////////////////////////////
// logical element operators
/// return a coord type containing the max element at each offset
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>,
typename ...Args
>
constexpr auto
min (K a, K b, Args &&...args)
{
// the varargs must be the same types as the first two arguments
static_assert ((
... && std::is_same_v<
K,
std::decay_t<Args>
>
));
K out {};
for (std::size_t i = 0; i < K::elements; ++i)
out[i] = min (a[i], b[i], args[i]...);
return out;
}
///------------------------------------------------------------------------
// /return a coord type containing the max element at each offset
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>,
typename ...Args
>
constexpr auto
max (K a, K b, Args &&...args)
{
static_assert ((
... && std::is_same_v<
K,
std::decay_t<Args>
>
));
K out {};
for (std::size_t i = 0; i < K::elements; ++i)
out[i] = max (a[i], b[i], args[i]...);
return out;
}
//-------------------------------------------------------------------------
/// returns a coordinate type where each element has been clamped to the
/// range [lo,hi].
///
/// we specifically do not allow different coordinate types for val, lo,
/// and hi because the min and max calls are ill definied for varying
/// types (not because varying types would not be useful).
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
clamp (K k, K lo, K hi)
{
assert (all (lo <= hi));
return max (min (k, hi), lo);
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
clamp (K k, typename K::value_type lo, K hi)
{
return clamp (k, K {lo}, hi);
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
clamp (K k, K lo, typename K::value_type hi)
{
return clamp (k, lo, K {hi});
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
clamp (K k, typename K::value_type lo, typename K::value_type hi)
{
return clamp (k, K {lo}, K {hi});
}
///------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
min (const K &k)
{
return *std::min_element (std::cbegin (k), std::cend (k));
}
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
max (const K &k)
{
return *std::max_element (std::cbegin (k), std::cend (k));
}
///////////////////////////////////////////////////////////////////////////
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
sum (const K &k)
{
// DO NOT USE cruft::sum(begin, end) from maths.hpp
//
// It would be nice to use kahan summation from maths.hpp but speed
// and simplicity is more important for these fixed sized
// coordinates. Infinities tend to crop up using these classes and
// they cause a few headaches in the kahan code.
//
// So, if the user wants kahan summation they can request it
// explicitly.
return std::accumulate (std::cbegin (k), std::cend (k), typename K::value_type{0});
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<is_coord_v<K>>
>
auto
product (K const& k)
{
typename K::value_type accum = 1;
for (auto i: k)
accum *= i;
return accum;
}
///////////////////////////////////////////////////////////////////////////
#define VECTOR_OP(OP) \
template < \
typename A, \
typename B, \
typename = std::enable_if_t< \
is_coord_v<A> && \
is_coord_v<B> && \
A::elements == B::elements && \
std::is_same_v< \
typename A::value_type, \
typename B::value_type \
>, \
void \
> \
> \
constexpr auto \
operator OP (const A a, const B b) \
{ \
vector<A::elements,bool> out {}; \
for (std::size_t i = 0; i < A::elements; ++i) \
out[i] = a[i] OP b[i]; \
return out; \
}
VECTOR_OP(<)
VECTOR_OP(>)
VECTOR_OP(<=)
VECTOR_OP(>=)
VECTOR_OP(&&)
VECTOR_OP(||)
#undef VECTOR_OP
#define SCALAR_OP(OP) \
template < \
typename K, \
typename U, \
typename = std::enable_if_t< \
is_coord_v<K> && \
std::is_arithmetic_v<U>, \
void \
> \
> \
constexpr auto \
operator OP (const K &k, const U u) \
{ \
vector<K::elements,bool> out {}; \
for (std::size_t i = 0; i < K::elements; ++i) \
out[i] = k[i] OP u; \
return out; \
} \
\
template < \
typename K, \
typename U, \
typename = std::enable_if_t< \
is_coord_v<K> && \
std::is_arithmetic_v<U>, \
void \
> \
> \
constexpr auto \
operator OP (const U u, const K &k) \
{ \
vector<K::elements,bool> out {}; \
for (std::size_t i = 0; i < K::elements; ++i) \
out[i] = u OP k[i]; \
return out; \
}
SCALAR_OP(<)
SCALAR_OP(>)
SCALAR_OP(<=)
SCALAR_OP(>=)
SCALAR_OP(==)
SCALAR_OP(&&)
SCALAR_OP(||)
#undef SCALAR_OP
///////////////////////////////////////////////////////////////////////////
namespace detail {
template <
std::size_t S,
template <std::size_t,typename> class K,
std::size_t ...I,
typename = std::enable_if_t<
is_coord_v<K<S,bool>>,
void
>
>
constexpr bool
any (const K<S,bool> k, std::index_sequence<I...>)
{
return (k[I] || ...);
}
};
///---------------------------------------------------------------------------
/// returns true if any element is true.
///
/// this function must be suitable for use in static_assert, so it must remain
/// constexpr.
///
/// we would ideally use std::any_of, but it is not constexpr.
/// we would ideally use range-for, but cbegin is not constexpr.
/// so... moar templates.
template <
std::size_t S,
template <std::size_t,typename> class K,
typename = std::enable_if_t<
is_coord_v<K<S,bool>>, void
>,
typename Indices = std::make_index_sequence<S>
>
constexpr
bool
any (const K<S,bool> k)
{
return detail::any (k, Indices{});
}
///------------------------------------------------------------------------
/// returns true if the value is true.
///
/// provided so that templates may operate with the same calls for vectors
/// and scalars. eg, any (t >= 0 && t <= 1) should work for arbitrary
/// types.
constexpr
bool any (const bool val)
{
return val;
}
///////////////////////////////////////////////////////////////////////////
namespace detail {
template <
std::size_t S,
template <std::size_t,typename> class K,
std::size_t ...I,
typename = std::enable_if_t<
is_coord_v<K<S,bool>>,
void
>
>
constexpr bool
all (const K<S,bool> k, std::index_sequence<I...>)
{
return (k[I] && ...);
}
}
//-------------------------------------------------------------------------
/// returns true if all elements are true.
///
/// this function must be suitable for use in static_assert, so it must be
/// constexpr.
///
/// we would ideally use std::all_of, but it is not constexpr.
/// we would ideally use range-for, but cbegin is not constexpr.
/// so... moar templates.
template <
std::size_t S,
template <std::size_t,typename> class K,
typename = std::enable_if_t<
is_coord_v<K<S,bool>>, void
>,
typename Indices = std::make_index_sequence<S>
>
constexpr
bool
all (const K<S,bool> k)
{
return detail::all (k, Indices {});
}
///------------------------------------------------------------------------
/// returns true if the value is true.
///
/// provided so that templates may operate with the same calls for vectors
/// and scalars. eg, all (t >= 0 && t <= 1) should work for either type.
constexpr bool
all (bool val)
{
return val;
}
///------------------------------------------------------------------------
/// returns an instance of K elementwise using a when s is true, and b
/// otherwise. ie, k[i] = s[i] ? a[i] : b[i];
///
/// corresponds to the function `select' from OpenCL.
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
select (vector<K::elements,bool> s, K a, K b)
{
K k {};
for (std::size_t i = 0; i < K::elements; ++i)
k[i] = s[i] ? a[i] : b[i];
return k;
}
template <
size_t S,
typename T,
typename K,
typename = std::enable_if_t<
is_coord_v<K> && !is_coord_v<T>
>
>
constexpr auto
select (vector<S,bool> s, K a, T b)
{
return select (s, a, K {b});
}
template <
size_t S,
typename T,
typename = std::enable_if_t<!is_coord_v<T>>
>
constexpr auto
select (vector<S,bool> s, T a, T b)
{
return select (s, vector<S,T> {a}, vector<S,T> {b});
}
///////////////////////////////////////////////////////////////////////////
// return the componentwise floor of the coordinate type
//
// don't use std::floor, it's _really_ slow in comparison.
//
// ideally we'd use SIMD or other more useful instructions here.
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K> && std::is_floating_point_v<typename K::value_type>
>
>
constexpr auto
floor (const K &k)
{
K out {};
std::transform (
std::cbegin (k),
std::cend (k),
std::begin (out),
[] (auto i) {
return i >= 0 ? static_cast<intmax_t> (i) : static_cast<intmax_t> (i) - 1;
}
);
return out;
}
//-------------------------------------------------------------------------
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K> && std::is_floating_point_v<typename K::value_type>
>
> constexpr auto
ceil (K const &k)
{
K res {};
for (std::size_t i = 0; i < K::elements; ++i)
res[i] = std::ceil (k[i]);
return res;
}
///------------------------------------------------------------------------
/// Return the fractional part of a real value.
///
/// This is an extraordinarily naive implementation. We avoid doing
/// explicit casts here in the hope that floor and sub is more efficient
/// (ie, keeps floats as floats in registers).
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K> &&
std::is_floating_point_v<
typename K::value_type
>
>
>
constexpr auto
frac (const K k)
{
return k - floor (k);
}
///////////////////////////////////////////////////////////////////////////
/// shifts all elements `num' indices to the right, setting the left-most
/// `num' indices to the value `fill'.
///
/// num must be between 0 and S. when 0 it is equivalent to an ordinary
/// fill, when S it is equivalent to a noop.
template<
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
rshift (const K k, const int num, const K fill)
{
CHECK_INCLUSIVE (num, 0, int (K::elements));
K res {};
std::copy_n (std::cbegin (k), K::elements - num, std::begin (res) + num);
std::copy_n (std::cbegin (fill), num, std::begin (res));
return res;
}
//-------------------------------------------------------------------------
template<
typename K,
typename = std::enable_if_t<
is_coord_v<K>, void
>
>
constexpr auto
rshift (const K k, const int num, typename K::value_type fill)
{
return rshift (k, num, K {fill});
}
template <
typename K,
typename = std::enable_if_t<
is_coord_v<K>
>
>
constexpr auto
lshift (const K k, const int places, typename K::value_type fill)
{
K res {};
std::copy_n (std::cbegin (k) + places, K::elements - places, std::begin (res));
std::fill_n (std::begin (res) + K::elements - places, places, fill);
return res;
}
//-------------------------------------------------------------------------
template <
std::size_t S,
typename T
>
vector<S,T>
to_radians (vector<S,T> const &val)
{
return ::cruft::invoke<vector<S,T>> (
::cruft::sin<T>,
val
);
}
//-------------------------------------------------------------------------
template <
std::size_t S,
typename T,
typename = std::enable_if_t<std::is_floating_point_v<T>>
>
vector<S,bool>
isfinite (vector<S,T> const &val)
{
vector<S,bool> res;
for (std::size_t i = 0; i < S; ++i)
res[i] = std::isfinite (val[i]);
return res;
}
}
///////////////////////////////////////////////////////////////////////////////
#include <tuple>
namespace std {
/// returns the dimensions of a coordinate type.
///
/// specifically required for structured bindings support.
///
/// \tparam S dimensions
/// \tparam T data type
/// \tparam K coordinate class
template <
std::size_t S,
typename T,
template <std::size_t,typename> typename K
>
class tuple_size<K<S,T>> : public std::enable_if_t<
::cruft::is_coord_v<K<S,T>>,
std::integral_constant<std::size_t, S>
> { };
/// indicates the type at a given index of a coordinate type
///
/// specifically required for structured bindings support.
///
/// \tparam I data index
/// \tparam S dimensionality of the coordinate
/// \tparam T data type for the coordinate
/// \tparam K the underlying coordinate class
template <
std::size_t I,
std::size_t S,
typename T,
template <std::size_t,typename> typename K
>
class tuple_element<I,K<S,T>> : public std::enable_if<
::cruft::is_coord_v<K<S,T>>,
T
> {};
}
///////////////////////////////////////////////////////////////////////////////
#include "../hash.hpp"
namespace std {
//-------------------------------------------------------------------------
template <
std::size_t S,
typename T,
template <std::size_t,typename> typename K
>
struct hash<K<S,T>> : enable_if<
::cruft::is_coord_v<K<S,T>>
> {
constexpr std::size_t
operator() (K<S,T> k) const noexcept
{
std::size_t v = 0xdeadbeef;
for (T const &t: k)
v = ::cruft::hash::mix (t, v);
return v;
}
};
//-------------------------------------------------------------------------
template <
typename CoordT,
typename = std::enable_if_t<
::cruft::is_coord_v<CoordT>, void
>
>
auto cos (CoordT val)
{
return ::cruft::invoke<CoordT> (::cruft::cos<typename CoordT::value_type>, val);
}
//-------------------------------------------------------------------------
template <
typename CoordT,
typename = std::enable_if_t<
::cruft::is_coord_v<CoordT>, void
>
>
auto sin (CoordT val)
{
return ::cruft::invoke<CoordT> (::cruft::sin<typename CoordT::value_type>, val);
}
};