spacebox/src/Attributes.hpp

294 lines
12 KiB
C++

/*!<pre>
* /\ +------------------------------------------------------+
* ____/ \____ /| - Open source game framework licensed to freely use, |
* \ / / | copy, modify and sell without restriction |
* +--\ ^__^ /--+ | |
* | ~/ \~ | | - created for <https://foam.shampoo.ooo> |
* | ~~~~~~~~~~~~ | +------------------------------------------------------+
* | SPACE ~~~~~ | /
* | ~~~~~~~ BOX |/
* +--------------+ </pre>
*
* Attributes
* ==========
*
* 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 decimal precision are lost
* {-5, 55, -555},
* 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.
*
* @param coordinate_0 x-coordinate
* @param coordinate_1 y-coordinate
* @param coordinate_2 z-coordinate (optional)
* @param coordinate_3 w-coordinate (optional)
*/
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.
*
* @param index vertex index
* @return std::vector of type VertexType
*/
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.
*
* @param outer vertex index
* @param inner coordinate index within vertex
* @return value of type CoordinateType
*/
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.
*
* @return std::vector of type VertexType
*/
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