attributes object for holding generic gl vertices

This commit is contained in:
frank 2021-10-13 00:34:11 -04:00
parent a6bcc2a4d9
commit 1690bb5f19
3 changed files with 367 additions and 14 deletions

131
src/Attributes.cpp Normal file
View File

@ -0,0 +1,131 @@
/* /\ +--------------------------------------------------------------+
____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - originally created at [http://nugget.fun] |
| ~~~~~~~~~~~~ | +--------------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+--------------+ */
#include "Attributes.hpp"
/* Returns the count of attributes */
std::size_t sb::Attributes::count() const
{
return std::visit([] (const auto& vector) -> std::size_t {
/* omit size check for the monostate (uninitialized attributes) variant */
if constexpr (!std::is_same_v<std::decay_t<decltype(vector)>, std::monostate>)
{
return vector.size();
}
else
{
return 0;
}
}, vertices);
}
/* Returns the size in bytes of the object's underlying vector of vertices. This can be passed to OpenGL
* along with the memory pointer when copying vertices to the GPU. */
std::size_t sb::Attributes::size() const
{
return std::visit([] (const auto& vector) -> std::size_t {
/* omit size check for the monostate (uninitialized attributes) variant */
if constexpr (!std::is_same_v<std::decay_t<decltype(vector)>, std::monostate>)
{
return vector.size() * sizeof(vector.front());
}
else
{
return 0;
}
}, vertices);
}
/* Returns a pointer to the first vertex in the object's underlying vector of vertices. This can be used
* along with the size function to pass the raw bytes of the vertices to the GPU. */
sb::Attributes::operator const void*() const
{
return std::visit([] (const auto& vector) -> const void* {
/* return nullptr for the monostate (uninitialized attributes) variant */
if constexpr (!std::is_same_v<std::decay_t<decltype(vector)>, std::monostate>)
{
return vector.data();
}
else
{
return nullptr;
}
}, vertices);
}
/* Extend to include these attributes at the end. The argument will be automatically converted to an
* Attributes object, so the vertices can be in any of the Attributes constructor formats. If no attributes
* have been constructed or initialized, the current vertices will be set to these vertices. Otherwise,
* vertices will be inserted at the end of the current vertices. */
void sb::Attributes::add(const Attributes& other)
{
/* If the variant is std::monostate, these are the first attributes, so set vertices to these */
if (std::holds_alternative<std::monostate>(vertices))
{
vertices = other.vertices;
}
else
{
/* Visit each of the current variants and in each, visit each of the other Attributes's variants.
* In every possible combination between the variants of both attributes, check to see if the variant
* types are compatible. If so, add the insert code to this version of the templated function */
bool found = false;
std::visit([&] (auto& vector) {
using Type = std::decay_t<decltype(vector)>;
if constexpr (!std::is_same_v<Type, std::monostate>)
{
using VertexType = typename Type::value_type;
std::visit([&] (auto& other_vector) {
using OtherType = std::decay_t<decltype(other_vector)>;
/* Check if current vertex type and current other vertex type are compatible */
if constexpr (!std::is_same_v<OtherType, std::monostate>)
{
using OtherVertexType = typename OtherType::value_type;
if constexpr (std::is_convertible_v<OtherVertexType, VertexType>)
{
vector.insert(vector.end(), other_vector.begin(), other_vector.end());
found = true;
}
}
}, other.vertices);
}
}, vertices);
if (!found)
{
std::ostringstream message;
message << "warning: " << other << " was not added to the attributes because its type is incompatible";
sb::Log::log(message);
}
}
}
/* Add attributes to the end count number of times. If count is 1, this has the same effect as the
* add function. */
void sb::Attributes::extend(const Attributes& other, std::size_t count)
{
while (count--)
{
add(other);
}
}
/* Overload the stream operator to support attributes. Add a string representation of the vertices to
* the output stream. Since this is defined as a friend function and isn't in the global scope, it should
* prevent it being looked up with arguments other than attributes. */
std::ostream& sb::operator<<(std::ostream& out, const Attributes& attributes)
{
std::visit([&] (const auto& vector) {
if constexpr (!std::is_same_v<std::decay_t<decltype(vector)>, std::monostate>)
{
out << vector;
}
}, attributes.vertices);
return out;
}

222
src/Attributes.hpp Normal file
View File

@ -0,0 +1,222 @@
/* /\ +--------------------------------------------------------------+
____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - originally created at [http://nugget.fun] |
| ~~~~~~~~~~~~ | +--------------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+--------------+
[Attributes.hpp]
An Attributes object is a container that acts as a limited, but dynamically typed vector for
vertex properties to be passed to a GL buffer object and described by a vertex array object.
It uses the variant library and type inference to determine the vertex type for an object. The
appropriate variant will be applied based on the first vertices submitted to the object.
The object can contain float, int, or unsigned int vertices of any dimension from 1D to 4D. It
can also contain bool vertices from 2D to 4D. Vertices can be submitted at initialization through
the constructor or added and appended later using the add function. If the object is initialized
without any vertices, it will be in an empty state indicated by the std::monostate variant
alternative.
The constructor accepts initializer lists, so unknown types can be submitted as long as a single type
can be inferred. For example,
Attributes({1.0f, 0.5f, -1.0f}); // glm::vec3 attributes containing one vertex
Attributes({ // glm::vec3 attributes containing two vertices
{1.0f, 0.5f, -1.0f},
{0.0f, 1.0f, 1.0f}
});
Attributes({1, 2}); // glm::ivec2 attributes containing one vertex
Attributes(glm::uvec2{1, 2}); // glm::uvec2 attributes containing one vertex
Attributes(1, 2); // same as Attributes({1, 2}), each argument is treated as a
// coordinate
Attributes({ // inferred as glm::uvec3, so the -5, -555, and the
{-5, 55, -555}, // decimal precision are lost
glm::uvec3{8, 88, 888},
{9.9, 9.99, 9.999}
});
The add function also accepts initializer lists and any type that can be converted into the original.
sb::Attributes attr, attr2, attr3, attr4;
attr.add(420);
attr.add({69, 9000});
attr.add({});
attr.add({{1}});
std::cout << attr << std::endl;
attr2.add(glm::ivec4{1, 1, 2, 3});
attr2.add({{5.0f, 8.0f, 13.0f, 21.0f}, {34.0f, 55.0f, 89.0f, 144.0f}});
attr2.add({glm::uvec4{5, 8, 13, 21}, glm::uvec4{34, 55, 89, 144}});
attr2.add({0.0f, 0.0f, 0.0f, 0.0f});
std::cout << attr2 << std::endl;
attr3.add(1.1f);
attr3.add(2);
attr3.add(std::vector<std::uint32_t>{3, 4});
std::cout << attr3 << std::endl;
attr3 = attr2;
attr2 = attr4;
std::cout << attr3 << std::endl;
std::cout << attr2 << std::endl;
attr4 = sb::Attributes({{-5, 55, -555}, glm::ivec3{8, 88, 888}, {9.99, 9.9, 9.0}});
attr4.add(1000, 10000, 100000);
attr4.add(1, 11);
std::cout << attr4 << std::endl;
attr2 = sb::Attributes(std::vector<float>{5.5, 6.6, 7.7, 8.8});
attr2.add(glm::vec2{9.9f, 10.10f});
std::cout << attr2 << std::endl;
This prints
warning: was not added to the attributes because its type is incompatible
{ 420 69 9000 1 }
warning: { 0 0 0 0 } was not added to the attributes because its type is incompatible
{ {1, 1, 2, 3} {5, 8, 13, 21} {34, 55, 89, 144} {5, 8, 13, 21} {34, 55, 89, 144} }
{ 1.1 2 3 4 }
{ {1, 1, 2, 3} {5, 8, 13, 21} {34, 55, 89, 144} {5, 8, 13, 21} {34, 55, 89, 144} }
warning: { {1, 11} } was not added to the attributes because its type is incompatible
{ {-5, 55, -555} {8, 88, 888} {9, 9, 9} {1000, 10000, 100000} }
warning: { {9.9, 10.1} } was not added to the attributes because its type is incompatible
{ 5.5 6.6 7.7 8.8 }
*/
#ifndef SB_ATTRIBUTES_H_
#define SB_ATTRIBUTES_H_
#include <any>
#include <ostream>
#include <vector>
#include <utility>
#include <type_traits>
#include <typeinfo>
#include <typeindex>
#include <initializer_list>
#include <variant>
#include "glm/glm.hpp"
#include "Log.hpp"
#include "extension.hpp"
namespace sb
{
/* Calls to the stream operator from within this namespace will search the global namespace as well as the current */
using ::operator<<;
class Attributes;
std::ostream& operator<<(std::ostream&, const Attributes&);
class Attributes
{
private:
/* Every type of vertex in the glm vertex types (bool, unsigned int, int, float) from 1D to 4D is included in this variant.
* Each variant alternative is a vector of vertex type, so only homogenous vectors can be used. Index 0 is the std::monospace
* alternative, which is used to default initialize Attributes to an empty state where no variant alternative is selected.
* 1D vertices are specified by vectors of scalars, rather than the 1D glm vertex types. The std::vector<bool> alternative is
* not included because its specialization doesn't include a data member function */
using Vertices = std::variant<std::monostate, std::vector<std::uint32_t>, std::vector<std::int32_t>, std::vector<float>,
std::vector<glm::bvec2>, std::vector<glm::uvec2>, std::vector<glm::ivec2>, std::vector<glm::vec2>,
std::vector<glm::bvec3>, std::vector<glm::uvec3>, std::vector<glm::ivec3>, std::vector<glm::vec3>,
std::vector<glm::bvec4>, std::vector<glm::uvec4>, std::vector<glm::ivec4>, std::vector<glm::vec4>>;
Vertices vertices;
public:
/* The default constructor creates an empty set of attributes. An empty set of attributes is indicated by
* the class's vertex variant having the std::monostate variant. */
Attributes() {};
/* Construct a new Attributes object with vertices set to the vertices contained in this vector */
template<typename Type>
Attributes(const std::vector<Type>& vertices) : vertices(vertices)
{
/* debug message */
std::ostringstream message;
message << "added vertex ";
for (const Type& vertex : vertices)
{
message << vertex << " ";
}
sb::Log::log(message, sb::Log::DEBUG);
}
/* Add a vertex. The vertex can be any of the variant types. */
template<typename Type>
Attributes(const Type& vertex) : Attributes({vertex}) {}
/* Add a vertex by specifying each coordinate as a separate argument. All arguments must have idential type. */
template<typename XType, typename... CoordinateTypes,
std::enable_if_t<(... && std::is_same_v<CoordinateTypes, XType>), std::nullptr_t> = nullptr>
Attributes(const XType& coordinate_0, const CoordinateTypes&... coordinates) :
Attributes({std::initializer_list<XType>({coordinate_0, coordinates ...})}) {}
/* Add vertices by passing an uninitialized list of vertices. This template applies to a list of
* vertices where the list type is undeclared but the containing vertices have been initialized
* or the type can be inferred because the vertices are 1D scalars */
template<typename VertexType>
Attributes(const std::initializer_list<VertexType>& vertices) :
Attributes(std::vector<VertexType>(vertices.begin(), vertices.end())) {}
/* Add vertices by passing a two-dimensional initializer list, a list of uninitialized vertices.
* The appropriate glm vertex size is applied by looking at the length of the first uninitialized
* vertex in the initializer list. */
template<typename CoordinateType>
Attributes(const std::initializer_list<std::initializer_list<CoordinateType>>& vertices)
{
for (auto& vertex : vertices)
{
const CoordinateType* coordinate = vertex.begin();
if (vertex.size() == 1)
{
add(vertex);
}
else if (vertex.size() == 2)
{
add(glm::vec<2, CoordinateType, glm::defaultp>({*coordinate++, *coordinate}));
}
else if (vertex.size() == 3)
{
add(glm::vec<3, CoordinateType, glm::defaultp>({*coordinate++, *coordinate++, *coordinate}));
}
else if (vertex.size() == 4)
{
add(glm::vec<4, CoordinateType, glm::defaultp>({*coordinate++, *coordinate++, *coordinate++, *coordinate}));
}
}
}
void add(const Attributes&);
/* Add a 2D, 3D or 4D vertex by specifying each coordinate as a separate argument. All arguments must have
* identical type. */
template<typename XType, typename YType, typename... CoordinateTypes,
std::enable_if_t<std::is_same_v<XType, YType> && (... && std::is_same_v<CoordinateTypes, XType>),
std::nullptr_t> = nullptr>
void add(const XType& coordinate_0, const YType& coordinate_1, const CoordinateTypes&... coordinates)
{
/* The arguments are converted to an initializer list of initializer lists, a 2D initializer list with
* the new vertex as its only element, and passed to the overloaded add function where the vertex is
* converted into an Attributes object */
add({std::initializer_list<XType>({coordinate_0, coordinate_1, coordinates ...})});
}
void extend(const Attributes&, std::size_t = 1);
void index(int);
int index() const;
void enable() const;
std::size_t size() const;
std::size_t count() const;
friend std::ostream& operator<<(std::ostream&, const Attributes&);
operator const void*() const;
};
}
#endif

View File

@ -190,21 +190,9 @@ int SDL_SetRenderDrawColor(SDL_Renderer*, const Color&);
int SDL_RenderFillRect(SDL_Renderer*, const Box&);
int lineColor(SDL_Renderer*, const Segment&, const Color&, std::uint8_t = 1);
template <typename T>
std::ostream& operator<<(std::ostream& out, const std::vector<T>& members)
{
out << "{ ";
for (const T& member : members)
{
out << member << " ";
}
out << "}";
return out;
}
/* Stream a text representation of a glm::vec of any type or dimension */
template<int dimensions, typename Type>
std::ostream& operator<<(std::ostream& out, const glm::vec<dimensions, Type, glm::defaultp> vec)
std::ostream& operator<<(std::ostream& out, const glm::vec<dimensions, Type, glm::defaultp>& vec)
{
out << "{" << vec.x << ", " << vec.y;
if constexpr (dimensions > 2)
@ -217,9 +205,21 @@ std::ostream& operator<<(std::ostream& out, const glm::vec<dimensions, Type, glm
}
out << "}";
return out;
}
std::ostream& operator<<(std::ostream&, const SDL_Color&);
/* Add the contents of a vector to the output stream */
template <typename Type>
std::ostream& operator<<(std::ostream& out, const std::vector<Type>& members)
{
out << "{ ";
for (const Type& member : members)
{
out << member << " ";
}
out << "}";
return out;
}
#endif