/*!
 *        /\         +------------------------------------------------------+ 
 *   ____/  \____   /| - Open source game framework licensed to freely use, |
 *   \          /  / |   copy, modify and sell without restriction          |
 * +--\ ^__^   /--+  |                                                      |
 * | ~/        \~ |  | - created for              |
 * | ~~~~~~~~~~~~ |  +------------------------------------------------------+
 * | SPACE ~~~~~  | /
 * |  ~~~~~~~ BOX |/
 * +--------------+                                                  
*/ #include "Attributes.hpp" /* Set the generic vertex attribute index for the attributes. It must be the same index that is * returned when glGetAttribLocation is given the shader program and variable name the attributes * will be associated with. The bind class member functions can automatically do that, and they * should be used instead of this function if possible. * * The index may be specified to GL in the shader source code using the location layout qualifier * in GLSL 3.3 and above. It can also be set automatically by GL when a shader program is linked. * In both cases, the return value of glGetAttribLocation should be passed to this function. * Otherwise, this index can be an arbitrary choice that is not greater than GL_MAX_VERTEX_ATTRIBS, * in which case it would be explicitly passed to glBindAttribLocation. * * Once assigned, the index can be passed to glVertexAttribPointer to set the properties of the * attributes on the GPU, either manually or automatically through the add member function in the * VBO class. */ void sb::Attributes::index(GLint index) { attribute_index = index; } /* Returns the generic vertex attribute index for the attributes. This index can be passed to * glBindAttribLocation to assign the index to a shader input variable if the index isn't already * assigned by a layout qualifier in the GLSL source code. The index should be passed to * glVertexAttribPointer to specify the location to use when rendering. */ GLint sb::Attributes::index() const { return attribute_index; } void sb::Attributes::offset(GLintptr offset) { _offset = offset; } /* 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::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::monostate>) { return vector.size() * sizeof(vector.front()); } else { return 0; } }, vertices); } void sb::Attributes::bind(const std::string& name, GLuint program) { index(glGetAttribLocation(program, name.c_str())); glVertexAttribPointer(index(), dimensions(), type(), normalized(), 0, reinterpret_cast(_offset)); } void sb::Attributes::enable() const { glEnableVertexAttribArray(*this); } void sb::Attributes::disable() const { glDisableVertexAttribArray(*this); } /* Return the GLenum that corresponds to the type of scalar being held in the vertices. This * will return either GL_FLOAT, GL_INT, GL_UNSIGNED_INT, GL_UNSIGNED_BYTE, GL_BOOL, or * GL_INVALID_ENUM. A return value of GL_INVALID_ENUM indicates an error meaning there are no * attributes and no type exists yet. */ GLenum sb::Attributes::type() const { return std::visit([] (const auto& vector) -> GLenum { using VectorType = std::decay_t; /* omit size check for the monostate (uninitialized attributes) variant */ if constexpr (!std::is_same_v) { using VertexType = typename VectorType::value_type; /* For 1D vertices, the vertex type will be scalar */ if constexpr (std::is_scalar_v) { using ScalarType = VertexType; if constexpr (std::is_floating_point_v) return GL_FLOAT; else if constexpr (std::is_unsigned_v) { if constexpr (sizeof(ScalarType) > 8) return GL_UNSIGNED_INT; else return GL_UNSIGNED_BYTE; } else return GL_INT; } /* For dimensions greater than 1, the scalar type will be the value_type of the vertex type */ else { using ScalarType = typename VertexType::value_type; if constexpr (std::is_floating_point_v) return GL_FLOAT; else if constexpr (std::is_unsigned_v) { if constexpr (sizeof(ScalarType) > 1) return GL_UNSIGNED_INT; else return GL_BOOL; } else return GL_INT; } } else { return GL_INVALID_ENUM; } }, vertices); } /* Return the number of dimensions in the vertices. If there are no vertices, return 0. */ std::size_t sb::Attributes::dimensions() const { return std::visit([] (const auto& vector) -> std::size_t { using VectorType = std::decay_t; if constexpr (!std::is_same_v) { using VertexType = typename VectorType::value_type; if constexpr (std::is_scalar_v) { return 1; } else { return VertexType::length(); } } else { return 0; } }, vertices); } /* Normalization isn't supported, so this always returns false */ bool sb::Attributes::normalized() const { return false; } /* 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::monostate>) { return vector.data(); } else { return nullptr; } }, vertices); } /* Attributes can be represented by their GL index when an int is requested. */ sb::Attributes::operator int() const { return index(); } 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(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; if constexpr (!std::is_same_v) { using VertexType = typename Type::value_type; std::visit([&] (auto& other_vector) { using OtherType = std::decay_t; /* Check if current vertex type and current other vertex type are compatible */ if constexpr (!std::is_same_v) { using OtherVertexType = typename OtherType::value_type; if constexpr (std::is_convertible_v) { 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); } } } 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) { out << ", std::monostate>) { out << vector; } }, attributes.vertices); out << ">"; return out; }