spacebox/src/Attributes.hpp

260 lines
12 KiB
C++

/* /\ +--------------------------------------------------------------+
____/ \____ /| - 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 Open GL */
#if defined(__EMSCRIPTEN__)
#include <GL/glew.h>
#elif defined(__ANDROID__) || defined(ANDROID)
#include <GLES3/gl3.h>
#include <GLES3/gl3ext.h>
#else
#include "glew/glew.h"
#endif
#include <ostream>
#include <vector>
#include <variant>
#include <initializer_list>
#include <type_traits>
#include <utility>
#include "glm/glm.hpp"
#include "Log.hpp"
#include "extension.hpp"
namespace sb
{
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::monostate
* alternative, which is used here for default initialization to indicate Attributes are in an empty state where no variant
* alternative is selected. 1D vertices are specified by vectors of scalars, rather than the 1D glm vertex types. 1D vertices
* also include an unsigned byte type. The std::vector<bool> alternative is not included because it is a special case of
* std::vector that doesn't have a data member function. */
using Vertices = std::variant<
std::monostate, std::vector<std::uint8_t>, 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;
GLint attribute_index = 0;
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 ...})});
}
/* Return a const reference to the vertex at the specified index in the attributes vector. */
template<typename VertexType>
const VertexType& read(std::size_t index) const
{
return std::get<std::vector<VertexType>>(vertices)[index];
}
/* 2D lookup of a value in the attributes. Return a const reference to the coordinate value at inner index
* within a vertex at outer index in the attributes vector. */
template<typename VertexType, typename CoordinateType = float>
const CoordinateType& read(std::size_t outer, std::size_t inner)
{
return std::get<std::vector<VertexType>>(vertices)[outer][inner];
}
/* Return the attributes as a reference to a typed vector. If the type is not the alternative in use by the
* attributes, std::bad_variant_access will be thrown. */
template<typename VertexType>
operator const std::vector<VertexType>&() const
{
return std::get<std::vector<VertexType>>(vertices);
}
void extend(const Attributes&, std::size_t = 1);
void index(GLint);
GLint index() const;
void bind(GLuint, const std::string&);
void bind(std::uint32_t, GLuint, const std::string&);
void enable() const;
void disable() const;
std::size_t size() const;
std::size_t count() const;
GLenum type() const;
std::size_t dimensions() const;
bool normalized() const;
friend std::ostream& operator<<(std::ostream&, const Attributes&);
operator const void*() const;
operator int() const;
};
}
#endif