Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1045 lines (868 sloc) 43.3 KB
// Copyright 2017-2019, Nicholas Sharp and the Polyscope contributors. http://polyscope.run.
#pragma once
#include "polyscope/messages.h"
#include "polyscope/utilities.h"
#include <type_traits>
#include <vector>
// This header contains a collection of template functions which enable Polyscope to consume user-defined types, so long
// as they can be accessed by one of several mechanisms.
// clang-format off
// == "How the heck do these work", abridged version:
//
// Nicholas Sharp (nsharp@cs.cmu.edu)
//
// (This is Nick's best attempt to explain these functions, during a glorious
// few days after writing them in July 2019 when he sorta understood how they
// work. An actual C++ expert could certainly give a better explanation. Pardon
// misused terminology.)
//
// These templates abuse C++ to consider many version of a function, and pick
// the one which will compile. We use technique to heuristically convert
// unknown user types to a standard format.
//
// This isn't really something it seems like the language supports, but
// technically you can do it, thanks to a "feature" called SFINAE
// (Substitution Failure Is Not An Error). SFINAE is based on a quirk of
// how function calls get resolved by the compiler:
//
// (1) The compiler assembles a list of all available functions whose names
// match a given function call.
//
// (2) The compiler attempts to substitute types in to the function signatures
// (aka templates, parameters, and return types) from (1). If any of these
// substitutions fail, that function is discarded, but processing
// continues (this is SFINAE---surprisingly, it's not a compiler error).
//
// (3)
// (a) If no valid function overloads remain, an error is thrown, no
// function exists.
// (b) If just one overload remains, it is used.
// (c) If multiple valid overloads remain, they are ranked, according
// to a few rules like preferring not to use variadic functions,
// and preferring functions that do not require implicit conversion
// of parameters over those that do. If there is a unique highest-
// ranked function, it is used. Otherwise, an error is issued about
// ambiguous function calls.
//
// We utilize SFINAE by ensuring that versions of a function which are not
// applicable get discarded during step (2). There are a few ways to do this,
// but we'll do it by adding extra template types with default values that
// fail to resolve.
//
// Here's a quick example to get the idea across. Suppose we have a
// function `template<class T> f(T x)`, which will invoke `x.doStuff()`.
//
// We can add an extra template argument to f() to make sure
// x.doStuff() exists.
//
// template<typename T,
// typename C1 = decltype((*(T*)nullptr).doStuff())>
// void f(T x) { /* function body */ }
//
// unpacking this:
// - decltype() yields the type that the expression in its interior
// would return if it were evaluated (but it won't be evaluated).
// - The weird expression (*(T*)nullptr) results in an object of type
// T. Obviously it would segfault, but that's fine, it won't get
// evaluated, we're just using it to get an expression that includes
// and object of type T. Surprisingly, there's no easier way.
// - We then invoke .doStuff() on the T we just created; so the
// decltype() it's wrapped in can output the return type of
// doStuff().
//
// If T does have a doStuff() method, C1 will simply hold the return type
// of doStuff(). However, if it does not, the expression will be rejected,
// and this whole function will be rejected, so another candidate can be
// considered. And that's our core trick!
//
// There are a few other mechanisms we will make use of below that we
// should briefly mention:
//
//
// [std::is_same, std::enable_if] Sometimes we don't just need to check
// if a function exists, we also care about what it returns. Two helper
// templates from the stl can be used to get us there.
//
// std::is_same<U,V> has a constant member ::value, which is true if
// the two template arguments are the same type, and false otherwise.
//
// std::enable_if<B> has a member type ::type which is defined if
// the boolean condition B is true, and does not exist otherwise.
// (recall, if ::type doesn't exist, it'll halt substitution as
// we discussed in (2) above).
//
// Putting these two together we can require that a function x.doStuff()
// return an int with a recipe like
//
// template<typename T,
// typename C1 = typename std::enable_if<
// std::is_same<decltype((*(T*)nullptr).doStuff()), int>::value
// >::type>
// void f(T x) { /* function body */ }
//
//
// [PreferenceT<>] Case (3a) from the list above represents a problem for
// us. What if more than one of our candidate functions is valid, and
// passes the template checks? This would lead to errors about ambiguous
// function overloads. We need to make sure that when multiple functions
// are valid, one is always preferred over all others.
//
// Our mechanism to do so will be including an extra dummpy parameter
// PreferenceT<N> in every function's argument list, with a distinct
// value of N. Our Preference<N> is implicitly convertible to
// PreferenceT<N-1> (and so on). So if our initial function call uses
// PreferenceT<N_MAX>{}, it will match any of the overloads with lower-
// numbered PreferenceT<> parameters. However, since these lower-numbered
// functions involve implicit conversions, they will always have a lower
// priority and not be ambiguous!
namespace polyscope {
// == First, define two helper types:
// We'll use this to give precedence to overloads.
// There are several sets of functions below which have identical signatures, except the first argument is a
// PreferenceT<N> for a distinct value of N. Since PreferenceT<N> implicitly converts to PreferenceT<N-1>, a function
// call to f(PreferenceT<BIGGEST_N>, ...) _can_ match any of the variants, but will _prefer_ to match the one with the
// largest N which passes type substitution. This allows us to esacape overload ambiguity, and also explicitly order the
// heirarchy of overloads.
template <std::size_t N>
struct PreferenceT : PreferenceT<N - 1> {};
template <>
struct PreferenceT<0> {};
// Used to make static asserts give nice errors
template <typename T>
struct WillBeFalseT : std::false_type {};
// Get the inner type of a bracket-accessible type
template <typename T>
struct InnerType {
typedef typename std::remove_reference<decltype((*(T*)nullptr)[0])>::type type;
};
// =================================================
// ============ array size adapator
// =================================================
// Adaptor to return the number of elements in an array type
//
// The result is a function `size_t adaptorF_size(const T& inputData)`, which returns the number of elements in the
// array.
//
// The following hierarchy of strategies will be attempted, with decreasing precedence:
// - any user defined function `size_t adaptorF_custom_size(const T& inputData)`
// - the .rows() member function inputData.rows()
// - the .size() member function inputData.size()
// Note: this dummy function is defined so the non-dependent user function name will always resolve to something;
// some compilers will throw an error if the name doesn't resolve.
inline void adaptorF_custom_size(void* dont_use) {
// dummy function
}
// Highest priority: any user defined function
template <class T,
/* condition: user function exists and returns something that can be cast to size_t */
typename C1 = typename std::enable_if< std::is_same<decltype((size_t)adaptorF_custom_size(*(T*)nullptr)), size_t>::value>::type>
size_t adaptorF_sizeImpl(PreferenceT<3>, const T& inputData) {
return adaptorF_custom_size(inputData);
}
// Next: call T.rows()
template <class T,
/* condition: has .rows() method which returns something that can be cast to size_t */
typename C1 = typename std::enable_if<std::is_same<decltype((size_t)(*(T*)nullptr).rows()), size_t>::value>::type>
size_t adaptorF_sizeImpl(PreferenceT<2>, const T& inputData) {
return inputData.rows();
}
// Next: call T.size()
template <class T,
/* condition: has .size() method which returns something that can be cast to size_t */
typename C1 = typename std::enable_if< std::is_same<decltype((size_t)(*(T*)nullptr).size()), size_t>::value>::type>
size_t adaptorF_sizeImpl(PreferenceT<1>, const T& inputData) {
return inputData.size();
}
// Fall-through case: no overload found :(
// We use this to print a slightly less scary error message.
#ifndef POLYSCOPE_NO_STANDARDIZE_FALLTHROUGH
template <class T, class S>
size_t adaptorF_sizeImpl(PreferenceT<0>, const T& inputData) {
static_assert(WillBeFalseT<T>::value, "could not resolve valid adaptor for size of array-like data");
return std::vector<S>();
}
#endif
// General version, which will attempt to substitute in to the variants above
template <class T>
size_t adaptorF_size(const T& inputData) {
return adaptorF_sizeImpl(PreferenceT<3>{}, inputData);
}
// =================================================
// ============ array access adapator
// =================================================
// Adaptor to access elements in an array type.
//
// Suppose the user array type is T, which holds many elements of type S.
// Here, we abstract by writing a function which converts the entire input array to a std::vector<S>.
//
// The result is a function `std::vector<S> adaptorF_convertToStdVector(const T& inputData)`, which is templated on T
// and S.
//
// Note: it might be tempting to instead abstract via a function which which accesses the i'th element of the array,
// but that would require that array types be random-accessible. By going to a std::vector, we open the door to
// non-random-accessible input types like iterables.
//
// The following hierarchy of strategies will be attempted, with decreasing precedence:
// - user-defined adaptorF_custom_convertToStdVector()
// - bracket access
// - callable (parenthesis) access
// - iterable (begin() and end())
// Note: this dummy function is defined so the non-dependent user function name will always resolve to something;
// some compilers will throw an error if the name doesn't resolve.
inline void adaptorF_custom_convertToStdVector(void* dont_use) {
// dummy function
}
// Highest priority: user-specified function
template <class T, class S,
/* condition: user defined function exists and returns something that can be bracket-indexed to get an S */
typename C1 = typename std::enable_if< std::is_same<decltype((S)adaptorF_custom_convertToStdVector(*(T*)nullptr)[0]), S>::value>::type>
std::vector<S> adaptorF_convertToStdVectorImpl(PreferenceT<4>, const T& inputData) {
auto userVec = adaptorF_custom_convertToStdVector(inputData);
// If the user-provided function returns something else, try to convert it to a std::vector<S>.
// (handles e.g. case where user returns std::vector<float> but we want std::vector<double>)
// In the case where the user function already returns what we want, this costs us an extra copy...
// maybe one day we can dive back in to template land to remedy.
std::vector<S> out(userVec.size());
for (size_t i = 0; i < out.size(); i++) {
out[i] = userVec[i];
}
return out;
}
// Next: any bracket access operator
template <class T, class S,
/* condition: input can be bracket-indexed to get an S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T*)nullptr)[(size_t)0]), S>::value>::type>
std::vector<S> adaptorF_convertToStdVectorImpl(PreferenceT<3>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<S> dataOut(dataSize);
for (size_t i = 0; i < dataSize; i++) {
dataOut[i] = inputData[i];
}
return dataOut;
}
// Next: any callable access operator
template <class T, class S,
/* condition: input can be called (aka parenthesis-indexed) to get an S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T*)nullptr)((size_t)0)), S>::value>::type>
std::vector<S> adaptorF_convertToStdVectorImpl(PreferenceT<2>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<S> dataOut(dataSize);
for (size_t i = 0; i < dataSize; i++) {
dataOut[i] = inputData(i);
}
return dataOut;
}
// Next: anything iterable (begin(), end(), etc)
// Note: this test for iterable isn't perfect, might get tricked by something that almost-but-not-quite matches.
template <class T, class S,
/* condition: input has a begin() and end() function, both of which can be dereferenced to get an S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)*std::begin(*(T*)nullptr)), S>::value &&
std::is_same<decltype((S)*std::end(*(T*)nullptr)), S>::value>::type>
std::vector<S> adaptorF_convertToStdVectorImpl(PreferenceT<1>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<S> dataOut(dataSize);
size_t i = 0;
for (auto v : inputData) {
dataOut[i] = v;
i++;
}
return dataOut;
}
// Fall-through case: no overload found :(
// We use this to print a slightly less scary error message.
#ifndef POLYSCOPE_NO_STANDARDIZE_FALLTHROUGH
template <class T, class S>
std::vector<S> adaptorF_convertToStdVectorImpl(PreferenceT<0>, const T& inputData) {
static_assert(WillBeFalseT<T>::value, "could not resolve valid adaptor for accessing array-like data");
return std::vector<S>();
}
#endif
// General version, which will attempt to substitute in to the variants above
template <class S, class T>
std::vector<S> adaptorF_convertToStdVector(const T& inputData) {
return adaptorF_convertToStdVectorImpl<T, S>(PreferenceT<4>{}, inputData);
}
// =================================================
// ============ vector-2 access adapator
// =================================================
// Adaptor to access the i'th element of a fixed-sized 2D vector
//
// The result is a function
// template <class S, unsigned int I, class T>
// inline S adaptorF_accessVector2Value(const T& inVal);
// which accesses the vector `inVal` at index `I` and returns a scalar of type `S`.
//
//
// The following hierarchy of strategies will be attempted, with decreasing precedence:
// - any user defined function `S adaptorF_custom_accessVector2Value(const T& inputVec, unsigned int ind)`;
// - bracketed indices T[0] and T[1]
// - members .x and .y
// - members .u and .v
// - members .real() and .imag()
// Note: this dummy function is defined so the non-dependent user function name will always resolve to something;
// some compilers will throw an error if the name doesn't resolve.
inline void adaptorF_custom_accessVector2Value(void* dont_use) {
// dummy function
}
// Highest priority: any user defined function
template <unsigned int I, class T, class S,
/* condition: user function exists and retuns something that can be cast to an S */
typename C1 = typename std::enable_if< std::is_same<decltype((S)adaptorF_custom_accessVector2Value(*(T*)nullptr, 0)), S>::value>::type>
S adaptorF_accessVector2ValueImpl(PreferenceT<5>, const T& inputVec) {
static_assert(I < 2, "bad vector2 access");
return adaptorF_custom_accessVector2Value(inputVec, I);
}
// Next: bracket indices
template <unsigned int I, class T, class S,
/* condition: input can be bracket-index to get something that can be cast to an S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T*)nullptr)[0]), S>::value>::type>
S adaptorF_accessVector2ValueImpl(PreferenceT<4>, const T& inputVec) {
static_assert(I < 2, "bad vector2 access");
return (S)inputVec[I];
}
// Next: members .x and .y
template <unsigned int I, class T, class S,
/* condition: input has .x and .y members which give something that can be cast to an S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T*)nullptr).x), S>::value &&
std::is_same<decltype((S)(*(T*)nullptr).y), S>::value>::type>
S adaptorF_accessVector2ValueImpl(PreferenceT<3>, const T& inputVec) {
static_assert(I < 2, "bad vector2 access");
if (I == 0) {
return (S)inputVec.x;
} else {
return (S)inputVec.y;
}
}
// Next: members .u and .v
template <unsigned int I, class T, class S,
/* condition: input has .u and .v members which give something that can be cast to an S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T*)nullptr).u), S>::value &&
std::is_same<decltype((S)(*(T*)nullptr).v), S>::value>::type>
S adaptorF_accessVector2ValueImpl(PreferenceT<2>, const T& inputVec) {
static_assert(I < 2, "bad vector2 access");
if (I == 0) {
return (S)inputVec.u;
} else {
return (S)inputVec.v;
}
}
// Next: members .real() and .imag()
template <unsigned int I, class T, class S,
/* condition: input has .real() and .imag() member functions which give something that can be cast to an S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T*)nullptr).real()), S>::value &&
std::is_same<decltype((S)(*(T*)nullptr).imag()), S>::value>::type>
S adaptorF_accessVector2ValueImpl(PreferenceT<1>, const T& inputVec) {
static_assert(I < 2, "bad vector2 access");
if (I == 0) {
return (S)inputVec.real();
} else {
return (S)inputVec.imag();
}
}
// Fall-through case: no overload found :(
// We use this to print a slightly less scary error message.
#ifndef POLYSCOPE_NO_STANDARDIZE_FALLTHROUGH
template <unsigned int I, class T, class S>
S adaptorF_accessVector2ValueImpl(PreferenceT<0>, const T& inputVec) {
static_assert(WillBeFalseT<T>::value, "could not resolve valid accessor for 2D vector-like value");
return S();
}
#endif
// General version, which will attempt to substitute in to the variants above
// Templates:
// - S: output scalar type
// - I: index at which to access
// - T: input length-2 vector-like type
template <class S, unsigned int I, class T,
/* condition: I must be < 2 */
class C1 = typename std::enable_if< I<2 >::type>
S adaptorF_accessVector2Value(const T& inVal) {
return adaptorF_accessVector2ValueImpl<I, T, S>(PreferenceT<5>{}, inVal);
}
// =================================================
// ============ vector-3 access adapator
// =================================================
// Adaptor to access the i'th element of a fixed-sized 3D vector
//
// The result is a function
// template <class S, unsigned int I, class T>
// inline S adaptorF_accessVector3Value(const T& inVal);
// which accesses the vector `inVal` at index `I` and returns a scalar of type `S`.
//
//
// The following hierarchy of strategies will be attempted, with decreasing precedence:
// - any user defined function `S adaptorF_custom_accessVector3Value(const T& inputVec, unsigned int ind)`;
// - bracketed indices T[0], T[1], T[2]
// - members .x .y .z
// Note: this dummy function is defined so the non-dependent user function name will always resolve to something;
// some compilers will throw an error if the name doesn't resolve.
inline void adaptorF_custom_accessVector3Value(void* dont_use) {
// dummy function
}
// Highest priority: any user defined function
template <unsigned int I, class T, class S,
/* condition: user function exists and returns something that can be cast to S */
typename C1 = typename std::enable_if< std::is_same<decltype((S)adaptorF_custom_accessVector3Value(*(T*)nullptr, 0)), S>::value>::type>
S adaptorF_accessVector3ValueImpl(PreferenceT<3>, const T& inputVec) {
static_assert(I < 3, "bad vector3 access");
return adaptorF_custom_accessVector3Value(inputVec, I);
}
// Next: bracket indices
template <unsigned int I, class T, class S,
/* condition: input can be bracket-indexed to get something that can be cast to S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T*)nullptr)[0]), S>::value>::type>
S adaptorF_accessVector3ValueImpl(PreferenceT<2>, const T& inputVec) {
static_assert(I < 3, "bad vector3 access");
return (S)inputVec[I];
}
// Next: members .x .y .z
template <unsigned int I, class T, class S,
/* condition: input has .x .y .z members which hold something that can be cast to S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T*)nullptr).x), S>::value &&
std::is_same<decltype((S)(*(T*)nullptr).y), S>::value &&
std::is_same<decltype((S)(*(T*)nullptr).z), S>::value>::type>
S adaptorF_accessVector3ValueImpl(PreferenceT<1>, const T& inputVec) {
static_assert(I < 3, "bad vector3 access");
if (I == 0) {
return (S)inputVec.x;
} else if (I == 1) {
return (S)inputVec.y;
} else {
return (S)inputVec.z;
}
}
// Fall-through case: no overload found :(
// We use this to print a slightly less scary error message.
#ifndef POLYSCOPE_NO_STANDARDIZE_FALLTHROUGH
template <unsigned int I, class T, class S>
S adaptorF_accessVector3ValueImpl(PreferenceT<0>, const T& inputVec) {
static_assert(WillBeFalseT<T>::value, "could not resolve valid accessor for 3D vector-like value");
return S();
}
#endif
// General version, which will attempt to substitute in to the variants above
// Templates:
// - S: output scalar type
// - I: index at which to access
// - T: input length-3 vector-like type
template <class S, unsigned int I, class T,
/* condition: I must be < 3 */
class C1 = typename std::enable_if< I<3 >::type>
S adaptorF_accessVector3Value(const T& inVal) {
return adaptorF_accessVector3ValueImpl<I, T, S>(PreferenceT<3>{}, inVal);
}
// =================================================
// ============ array-of-vector access adapator
// =================================================
// Adaptor to convert an array of a dense vectors to a canonical representation. For instance, a list of N vector3's
// (like std::vector<glm::vec3>), or matrix-style representations, like Eigen::MatrixXd<N,3>.
//
// The output is a std::vector<O>, where O is an output type also given as a template argument. The output type must be
// subscriptable up to the inner dimension D, for instance one could use O = glm::vec3 or O = std::array<S,D>.
//
// The result is a function
// template <class O, unsigned int D, class T>
// inline std::vector<O> adaptorF_convertArrayOfVectorToStdVector(const T& inputData);
// which converts the input to a std::vector<O>.
//
//
// The following hierarchy of strategies will be attempted, with decreasing precedence:
// - any user defined function
// std::vector<std::array<F, D>> adaptorF_custom_convertArrayOfVectorToStdVector(const YOUR_TYPE& inputData);
// - dense callable (parenthesis) access (like T(i,j))
// - double bracket access (like T[i][j])
// - outer type bracket accessbile, inner anything convertible to Vector2/3
// - outer type iteratble, inner type anything convertible to Vector2/3
// - iterable bracket (like for(T val : inputData) { val[0] })
// Highest priority: user-specified function
// Note: this dummy function is defined so the non-dependent user function name will always resolve to something;
// some compilers will throw an error if the name doesn't resolve.
inline void adaptorF_custom_convertArrayOfVectorToStdVector(void* dont_use) {
// dummy function
}
template <
class O, unsigned int D, class T,
/* condition: user function exists and returns something that can be bracket-indexed to get an S */
typename C1 = typename std::enable_if<std::is_same<
decltype((typename InnerType<O>::type)(adaptorF_custom_convertArrayOfVectorToStdVector(*(T*)nullptr))[0][0]),
typename InnerType<O>::type>::value>::type>
std::vector<O> adaptorF_convertArrayOfVectorToStdVectorImpl(PreferenceT<8>, const T& inputData) {
// should be std::vector<std::array<SCALAR,D>>
auto userArr = adaptorF_custom_convertArrayOfVectorToStdVector(inputData);
// This results in an extra copy, which isn't reallllly necessary.
size_t dataSize = userArr.size();
std::vector<O> dataOut(dataSize);
for (size_t i = 0; i < dataSize; i++) {
for (size_t j = 0; j < D; j++) {
dataOut[i][j] = userArr[i][j];
}
}
return dataOut;
}
// Next: any dense callable (parenthesis) access operator
template <class O, unsigned int D, class T,
/* condition: input can be called with two integer arguments to get something that can be cast to the inner type of O */
typename C1 = typename std::enable_if<std::is_same<
decltype((typename InnerType<O>::type)(*(T*)nullptr)((size_t)0, (size_t)0)),
typename InnerType<O>::type>::value>::type>
std::vector<O> adaptorF_convertArrayOfVectorToStdVectorImpl(PreferenceT<7>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<O> dataOut(dataSize);
for (size_t i = 0; i < dataSize; i++) {
for (size_t j = 0; j < D; j++) {
dataOut[i][j] = inputData(i, j);
}
}
return dataOut;
}
// Next: any dense bracket access operator
template <class O, unsigned int D, class T,
/* condition: input can be bracket-indexed twice to get something that can be cast to the inner type of O */
typename C1 = typename std::enable_if<std::is_same<
decltype((typename InnerType<O>::type)(*(T*)nullptr)[(size_t)0][(size_t)0]),
typename InnerType<O>::type>::value>::type>
std::vector<O> adaptorF_convertArrayOfVectorToStdVectorImpl(PreferenceT<6>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<O> dataOut(dataSize);
for (size_t i = 0; i < dataSize; i++) {
for (size_t j = 0; j < D; j++) {
dataOut[i][j] = inputData[i][j];
}
}
return dataOut;
}
// Next: bracketed array of anything adaptable to vector3
template <class O, unsigned int D, class T,
/* helper type: inner type that results from bracket-indexing T */
typename C_INNER = typename std::remove_reference<decltype((*(T*)nullptr)[(size_t)0])>::type,
/* helper type: inner type of output O */
typename C_RES = typename InnerType<O>::type,
/* helper type: scalar type that results from a vector3 access on C_INNER */
typename C_INNER_SCALAR = decltype(adaptorF_accessVector3Value<C_RES, 0>((*(C_INNER*)nullptr))),
/* condition: output dimension must be 3 */
typename C1 = typename std::enable_if<D == 3>::type,
/* condition: the inner_scalar that comes from the vector3 unpack must match the requested inner type */
typename C2 = typename std::enable_if<std::is_same<C_INNER_SCALAR, C_RES>::value>::type>
std::vector<O> adaptorF_convertArrayOfVectorToStdVectorImpl(PreferenceT<5>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<O> dataOut(dataSize);
for (size_t i = 0; i < dataSize; i++) {
dataOut[i][0] = adaptorF_accessVector3Value<C_RES, 0>(inputData[i]);
dataOut[i][1] = adaptorF_accessVector3Value<C_RES, 1>(inputData[i]);
dataOut[i][2] = adaptorF_accessVector3Value<C_RES, 2>(inputData[i]);
}
return dataOut;
}
// Next: bracketed array of anything adaptable to vector2
template <class O, unsigned int D, class T,
/* helper type: inner type that results from bracket-indexing T */
typename C_INNER = typename std::remove_reference<decltype((*(T*)nullptr)[(size_t)0])>::type,
/* helper type: inner type of output O */
typename C_RES = typename InnerType<O>::type,
/* helper type: scalar type that results from a vector2 access on C_INNER */
typename C_INNER_SCALAR = decltype(adaptorF_accessVector2Value<C_RES, 0>((*(C_INNER*)nullptr))),
/* condition: output dimension must be 2 */
typename C1 = typename std::enable_if<D == 2>::type,
/* condition: the inner_scalar that comes from the vector2 unpack must match the requested inner type */
typename C2 = typename std::enable_if<std::is_same<C_INNER_SCALAR, C_RES>::value>::type>
std::vector<O> adaptorF_convertArrayOfVectorToStdVectorImpl(PreferenceT<4>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<O> dataOut(dataSize);
for (size_t i = 0; i < dataSize; i++) {
dataOut[i][0] = adaptorF_accessVector2Value<C_RES, 0>(inputData[i]);
dataOut[i][1] = adaptorF_accessVector2Value<C_RES, 1>(inputData[i]);
}
return dataOut;
}
// Next: iterable array of anything adaptable to vector3
template <class O, unsigned int D, class T,
/* helper type: inner type that results from dereferencing begin() */
typename C_INNER = typename std::remove_reference<decltype(*(*(T*)nullptr).begin())>::type,
/* helper type: inner type that results from dereferencing end() */
typename C_INNER_END = typename std::remove_reference<decltype(*(*(T*)nullptr).end())>::type,
/* helper type: inner type of output O */
typename C_RES = typename InnerType<O>::type,
/* helper type: scalar type that results from a vector3 access on C_INNER */
typename C_INNER_SCALAR = decltype(adaptorF_accessVector3Value<C_RES, 0>((*(C_INNER*)nullptr))),
/* condition: output dimension must be 3 */
typename C1 = typename std::enable_if<D == 3>::type,
/* condition: the inner_scalar that comes from the vector3 unpack must match the requested inner type */
typename C2 = typename std::enable_if<std::is_same<C_INNER_SCALAR, C_RES>::value>::type,
/* condition: the type that comes from begin() must match the one from end() */
typename C3 = typename std::enable_if<std::is_same<C_INNER, C_INNER_END>::value>::type>
std::vector<O> adaptorF_convertArrayOfVectorToStdVectorImpl(PreferenceT<3>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<O> dataOut(dataSize);
size_t i = 0;
for (auto v : inputData) {
dataOut[i][0] = adaptorF_accessVector3Value<C_RES, 0>(v);
dataOut[i][1] = adaptorF_accessVector3Value<C_RES, 1>(v);
dataOut[i][2] = adaptorF_accessVector3Value<C_RES, 2>(v);
i++;
}
return dataOut;
}
// Next: iterable array of anything adaptable to vector2
template <class O, unsigned int D, class T,
/* helper type: inner type that results from dereferencing begin() */
typename C_INNER = typename std::remove_reference<decltype(*(*(T*)nullptr).begin())>::type,
/* helper type: inner type that results from dereferencing end() */
typename C_INNER_END = typename std::remove_reference<decltype(*(*(T*)nullptr).end())>::type,
/* helper type: inner type of output O */
typename C_RES = typename InnerType<O>::type,
/* helper type: scalar type that results from a vector2 access on C_INNER */
typename C_INNER_SCALAR = decltype(adaptorF_accessVector2Value<C_RES, 0>((*(C_INNER*)nullptr))),
/* condition: output dimension must be 2 */
typename C1 = typename std::enable_if<D == 2>::type,
/* condition: the inner_scalar that comes from the vector2 unpack must match the requested inner type */
typename C2 = typename std::enable_if<std::is_same<C_INNER_SCALAR, C_RES>::value>::type,
/* condition: the type that comes from begin() must match the one from end() */
typename C3 = typename std::enable_if<std::is_same<C_INNER, C_INNER_END>::value>::type>
std::vector<O> adaptorF_convertArrayOfVectorToStdVectorImpl(PreferenceT<2>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<O> dataOut(dataSize);
size_t i = 0;
for (auto v : inputData) {
dataOut[i][0] = adaptorF_accessVector2Value<C_RES, 0>(v);
dataOut[i][1] = adaptorF_accessVector2Value<C_RES, 1>(v);
i++;
}
return dataOut;
}
// Next: iterable (begin(), end(), etc) + bracket accesss
// Note: this test for iterable isn't perfect, might get tricked by something that almost-but-not-quite matches.
template <class O, unsigned int D, class T, typename C_RES = typename InnerType<O>::type,
/* condition: begin() and end() should return something bracket-indexable to yield the inner type of O */
typename C1 = typename std::enable_if<std::is_same<decltype((C_RES)(*std::begin(*(T*)nullptr))[0]), C_RES>::value &&
std::is_same<decltype((C_RES)(*std::end(*(T*)nullptr))[0]), C_RES>::value>::type>
std::vector<O> adaptorF_convertArrayOfVectorToStdVectorImpl(PreferenceT<1>, const T& inputData) {
size_t dataSize = adaptorF_size(inputData);
std::vector<O> dataOut(dataSize);
size_t i = 0;
for (auto v : inputData) {
for (size_t j = 0; j < D; j++) {
dataOut[i][j] = v[j];
}
i++;
}
return dataOut;
}
// Fall-through case: no overload found :(
// We use this to print a slightly less scary error message.
#ifndef POLYSCOPE_NO_STANDARDIZE_FALLTHROUGH
template <class O, unsigned int D, class T>
std::vector<O> adaptorF_convertArrayOfVectorToStdVectorImpl(PreferenceT<0>, const T& inputData) {
static_assert(WillBeFalseT<T>::value,
"could not resolve valid adaptor for accessing array-of-vectors-like input data");
return std::vector<O>();
}
#endif
// General version, which will attempt to substitute in to the variants above
template <class O, unsigned int D, class T>
std::vector<O> adaptorF_convertArrayOfVectorToStdVector(const T& inputData) {
return adaptorF_convertArrayOfVectorToStdVectorImpl<O, D, T>(PreferenceT<8>{}, inputData);
}
// =================================================
// ============ nested array access adapator
// =================================================
// Adaptor to convert an array of arrays to a canonical representation. Here, the array can be "ragged"--not all of
// the inner arrays need to have the same length (though they certainly may). Possible inputs might be a
// `std::vector<std::vector<size_t>>`, or an `Eigen::MatrixXd`.
//
// The output is a std::vector<std::vector<S>>, where S is an output scalar type also given as a template argument.
//
// The result is a function
// template <class S, class T>
// inline std::vector<std::vector<S>> adaptorF_convertNestedArrayToStdVector(const T& inputData);
//
//
// The following hierarchy of strategies will be attempted, with decreasing precedence:
// - any user defined function
// std::vector<std::vector<S>> adaptorF_custom_convertNestedArrayToStdVector(const YOUR_TYPE& inputData);
// - dense callable (parenthesis) access (like T(i,j)), on a type that supports .rows() and .cols()
// - recursive unpacking with bracket
// - recursive unpacking with parent
// - recursive unpacking with iterable
// Note: this dummy function is defined so the non-dependent name adaptorF_custom_convertArrayOfVectorToStdVector will
// always resolve to something; some compilers will throw an error if the name doesn't resolve.
inline void adaptorF_custom_convertNestedArrayToStdVector(void* dont_use) {
// dummy function
}
// Highest priority: user-specified function
template <class S, class T,
/* condition: user function must be return a nested std::vector of S */
typename C1 = typename std::enable_if<std::is_same<
decltype((S)(adaptorF_custom_convertNestedArrayToStdVector(*(T*)nullptr))[0][0]),
S>::value>::type>
std::vector<std::vector<S>> adaptorF_convertNestedArrayToStdVectorImpl(PreferenceT<5>, const T& inputData) {
// should be std::vector<std::vector<USER_SCALAR>>
auto userArr = adaptorF_custom_convertNestedArrayToStdVector(inputData);
// This results in an extra copy, which isn't reallllly necessary
size_t outerSize = userArr.size();
std::vector<std::vector<S>> dataOut(outerSize);
for (size_t i = 0; i < outerSize; i++) {
size_t innerSize = userArr[i].size();
dataOut[i].resize(innerSize);
for (size_t j = 0; j < innerSize; j++) {
dataOut[i][j] = userArr[i][j];
}
}
return dataOut;
}
// Next: any dense callable (parenthesis) access operator
template <class S, class T,
/* condition: must have .rows() function which return something like size_t */
typename C1 = typename std::enable_if<std::is_same<decltype((size_t)(*(T*)nullptr).rows()), size_t>::value>::type,
/* condition: must have .cols() function which return something like size_t */
typename C2 = typename std::enable_if<std::is_same<decltype((size_t)(*(T*)nullptr).cols()), size_t>::value>::type,
/* condition: must have be able to call with two size_t arguments to get something that can be cast to S */
typename C3 = typename std::enable_if<std::is_same<decltype((S)(*(T*)nullptr)((size_t)0, (size_t)0)), S>::value>::type>
std::vector<std::vector<S>> adaptorF_convertNestedArrayToStdVectorImpl(PreferenceT<4>, const T& inputData) {
size_t outerSize = (size_t)inputData.rows();
size_t innerSize = (size_t)inputData.cols();
std::vector<std::vector<S>> dataOut(outerSize);
for (size_t i = 0; i < outerSize; i++) {
dataOut[i].resize(innerSize);
}
for (size_t i = 0; i < outerSize; i++) {
for (size_t j = 0; j < innerSize; j++) {
dataOut[i][j] = inputData(i, j);
}
}
return dataOut;
}
// Next: recusive unpacking with bracket
template <class S, class T,
/* helper type: the result of bracket access on the outer type */
typename T_INNER = typename std::remove_reference<decltype((*(T*)nullptr)[0])>::type,
/* helper type: the result of running array conversion to S on the inner type */
typename T_INNER_RES = typename std::remove_reference<decltype(adaptorF_convertToStdVector<S>(*(T_INNER*)nullptr)[0])>::type,
/* condition: T_INNER_RES must be castable to the inner output type S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T_INNER_RES*)nullptr)), S>::value>::type>
std::vector<std::vector<S>> adaptorF_convertNestedArrayToStdVectorImpl(PreferenceT<3>, const T& inputData) {
size_t outerSize = adaptorF_size(inputData);
std::vector<std::vector<S>> dataOut(outerSize);
for (size_t i = 0; i < outerSize; i++) {
dataOut[i] = adaptorF_convertToStdVector<S>(inputData[i]);
}
return dataOut;
}
// Next: recusive unpacking with paren
template <class S, class T,
/* helper type: the result of paren access on the outer type */
typename T_INNER = typename std::remove_reference<decltype((*(T*)nullptr)(0))>::type,
/* helper type: the result of running array conversion to S on the inner type */
typename T_INNER_RES = typename std::remove_reference<decltype(adaptorF_convertToStdVector<S>(*(T_INNER*)nullptr)[0])>::type,
/* condition: T_INNER_RES must be castable to the inner output type S */
typename C1 = typename std::enable_if<std::is_same<decltype((S)(*(T_INNER_RES*)nullptr)), S>::value>::type>
std::vector<std::vector<S>> adaptorF_convertNestedArrayToStdVectorImpl(PreferenceT<2>, const T& inputData) {
size_t outerSize = adaptorF_size(inputData);
std::vector<std::vector<S>> dataOut(outerSize);
for (size_t i = 0; i < outerSize; i++) {
dataOut[i] = adaptorF_convertToStdVector<S>(inputData(i));
}
return dataOut;
}
// Next: recusive unpacking with iterable
template <class S, class T,
/* helper type: the result of dereferencing begin() on the outer type */
typename T_INNER = typename std::remove_reference<decltype(*(*(T*)nullptr).begin())>::type,
/* helper type: the result of dereferencing end() on the outer type */
typename T_INNER_END = typename std::remove_reference<decltype(*(*(T*)nullptr).end())>::type,
/* helper type: the result of running array conversion to S on the inner type */
typename T_INNER_RES = typename std::remove_reference<decltype(adaptorF_convertToStdVector<S>(*(T_INNER*)nullptr)[0])>::type,
/* condition: T_INNER_RES must be castable to the inner output type S */
typename C1 = typename std::enable_if< std::is_same<T_INNER, T_INNER_END>::value>::type,
/* condition: T_INNER_RES must be castable to the inner output type S */
typename C2 = typename std::enable_if<std::is_same<decltype((S)(*(T_INNER_RES*)nullptr)), S>::value>::type>
std::vector<std::vector<S>> adaptorF_convertNestedArrayToStdVectorImpl(PreferenceT<1>, const T& inputData) {
size_t outerSize = adaptorF_size(inputData);
std::vector<std::vector<S>> dataOut(outerSize);
size_t i = 0;
for (const auto& n : inputData) {
dataOut[i] = adaptorF_convertToStdVector<S>(n);
i++;
}
return dataOut;
}
// Fall-through case: no overload found :(
// We use this to print a slightly less scary error message.
#ifndef POLYSCOPE_NO_STANDARDIZE_FALLTHROUGH
template <class S, class T>
std::vector<std::vector<S>> adaptorF_convertNestedArrayToStdVector(PreferenceT<0>, const T& inputData) {
static_assert(WillBeFalseT<T>::value, "could not resolve valid adaptor for accessing nested-array-like input data");
return std::vector<std::vector<S>>();
}
#endif
// General version, which will attempt to substitute in to the variants above
template <class S, class T>
std::vector<std::vector<S>> adaptorF_convertNestedArrayToStdVector(const T& inputData) {
return adaptorF_convertNestedArrayToStdVectorImpl<S, T>(PreferenceT<5>{}, inputData);
}
// clang-format on
// =================================================
// ============ standardize functions
// =================================================
// These are utilitiy functions which use the adaptors above to do useful things, with nicer syntax.
// Check that a data array has the expected size
template <class T>
void validateSize(const T& inputData, std::vector<size_t> expectedSizes, std::string errorName = "") {
// No-op if no sizes given
if (expectedSizes.size() == 0) {
return;
}
size_t dataSize = adaptorF_size(inputData);
// Simpler error if only one size
if (expectedSizes.size() == 1) {
if (dataSize != expectedSizes[0]) {
error("Size validation failed on data array [" + errorName + "]. Expected size " +
std::to_string(expectedSizes[0]) + " but has size " + std::to_string(dataSize));
}
}
// General case
else {
// Return success if any sizes match
for (size_t possibleSize : expectedSizes) {
if (dataSize == possibleSize) {
return;
}
}
// Build a useful error message
std::string sizesStr = "{";
for (size_t possibleSize : expectedSizes) {
sizesStr += std::to_string(possibleSize) + ",";
}
sizesStr += "}";
error("Size validation failed on data array [" + errorName + "]. Expected size in " + sizesStr + " but has size " +
std::to_string(dataSize));
}
}
// Pass through to general version which takes a single expected size
template <class T>
void validateSize(const T& inputData, size_t expectedSize, std::string errorName = "") {
validateSize<T>(inputData, std::vector<size_t>{expectedSize}, errorName);
}
// Convert an array of scalar types
// class D: scalar data type
// class T: input array type
template <class D, class T>
std::vector<D> standardizeArray(const T& inputData) {
return adaptorF_convertToStdVector<D, T>(inputData);
}
// Convert an array of vector types
// class O: output inner vector type to put the result in. Will be bracket-indexed.
// (Polyscope pretty much always uses glm::vec2/3, std::vector<>, or std::array<>)
// unsigned int D: dimension of inner vector type
// class T: input array type
template <class O, unsigned int D, class T>
std::vector<O> standardizeVectorArray(const T& inputData) {
return adaptorF_convertArrayOfVectorToStdVector<O, D, T>(inputData);
}
// Convert a nested array where the inner types have variable length.
// class S: innermost scalar type for output
// class T: input nested array type
template <class S, class T>
std::vector<std::vector<S>> standardizeNestedList(const T& inputData) {
return adaptorF_convertNestedArrayToStdVector<S>(inputData);
}
} // namespace polyscope
You can’t perform that action at this time.