294 lines
12 KiB
C++
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
|