Introduction
After finishing most of the tutorials on https://learnopengl.com/ I started to encapsulate code in their respective classes and started to write a mesh class and a polygon class as my first primitive geometry object to figure out the structure of my code. There are a few questions about the code I'll add at the end and apart from them I am looking for possible improvement. Eventually I want to end up with a 2D sprite class and a tilemap class to efficiently render bigger maps. Currently all the code is inlined in header files and I will eventually split it into translation units once I am convinced about my design.
Vertex.hpp
/// Describes a custom vertex format structure that contains position, color,
/// and one set of texture coordinates.
struct FVertex final {
/// XYZ position.
glm::vec3 Position;
/// The vertex color.
glm::vec3 Color;
/// UV texture coordinates.
glm::vec2 TextureCoordinate;
};
Mesh.hpp
/// A mesh is a collection of vertices that define the geometry of an object.
class FMesh final {
public:
/// Creates a new FMesh.
explicit FMesh() { initialize(); };
/// Default destructor.
~FMesh() {
if (!IsInitialized) {
return;
}
glDeleteVertexArrays(1, &VertexArrayHandle);
glDeleteBuffers(1, &VertexBufferHandle);
IsInitialized = false;
}
/// Deleted copy-constructor.
FMesh(const FMesh &) = delete;
/// Deleted move-constructor.
FMesh(FMesh &&) = delete;
/// Deleted copy-assignment operator.
auto operator=(const FMesh &) -> FMesh & = delete;
/// Deleted move-assignment operator.
auto operator=(FMesh &&) -> FMesh & = delete;
/// Draws all vertices of this FMesh.
/// @param Mode Specifies what kind of primitives to render. Can be one of @p
/// GL_POINTS, @p GL_LINES, @p GL_LINE_STRIP, @p GL_LINE_LOOP, @p
/// GL_TRIANGLES, @p GL_TRIANGLE_STRIP or @p GL_TRIANGLE_FAN.
auto draw(const GLenum Mode) const -> void {
glBindVertexArray(VertexArrayHandle);
glDrawArrays(Mode, 0, static_cast<GLint>(Vertices.size()));
}
/// Sets the vertex with the specified index of this FMesh.
/// @param Index Index of the vertex to set.
/// @param Vertex Vertex data to store at the specified index.
/// @remark Before using this method you must call setVertexCount() first to
/// initialize this FMesh.
auto setVertex(const std::size_t Index, const FVertex &Vertex) -> void {
assert(Vertices.size() >= 2 &&
"The FMesh hasn't been initialized with setVertexCount.");
Vertices.at(Index) = Vertex;
update();
}
/// Sets the number of vertices of this FMesh.
/// @param Count Number of vertices of this FMesh.
auto setVertexCount(const std::size_t Count) -> void {
assert(Count >= 2 && "The FMesh must have at least two vertices.");
Vertices.resize(Count);
update();
}
private:
/// Initializes the OpenGL objects required for this FMesh.
auto initialize() -> void {
if (IsInitialized) {
return;
}
glGenVertexArrays(1, &VertexArrayHandle);
glGenBuffers(1, &VertexBufferHandle);
glBindVertexArray(VertexArrayHandle);
glBindBuffer(GL_ARRAY_BUFFER, VertexBufferHandle);
glBufferData(GL_ARRAY_BUFFER, Vertices.size() * sizeof(FVertex),
Vertices.data(), GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, decltype(FVertex::Position)::length(), GL_FLOAT,
GL_FALSE, sizeof(FVertex),
getOffsetPtrOf(&FVertex::Position));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, decltype(FVertex::Color)::length(), GL_FLOAT,
GL_FALSE, sizeof(FVertex),
getOffsetPtrOf(&FVertex::Color));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, decltype(FVertex::TextureCoordinate)::length(),
GL_FLOAT, GL_FALSE, sizeof(FVertex),
getOffsetPtrOf(&FVertex::TextureCoordinate));
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
IsInitialized = true;
}
/// Recompute the internal geometry of the FMesh. This function must be called
/// every time the vertices change.
auto update() const -> void {
assert(IsInitialized == true &&
"Cannot update FMesh that is not initialized.");
glBindBuffer(GL_ARRAY_BUFFER, VertexBufferHandle);
glBufferData(GL_ARRAY_BUFFER, Vertices.size() * sizeof(FVertex),
Vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
/// Boolean value indicating whether this FMesh has been intialized.
bool IsInitialized;
/// OpenGL handle to the vertex buffer object of this FMesh.
FHandle VertexBufferHandle;
/// OpenGL handle to the vertex array object of this FMesh.
FHandle VertexArrayHandle;
/// List of FVertex objects describing the geometry of this FMesh.
std::vector<FVertex> Vertices;
};
Polygon.hpp
/// A primitive shape with a finite number of straight line segments that form a
/// closed chain.
class FPolygon final {
public:
/// Creates a new FPolygon.
explicit FPolygon() = default;
/// Default destructor.
~FPolygon() = default;
/// Deleted copy-constructor.
FPolygon(const FPolygon &) = delete;
/// Deleted move-constructor.
FPolygon(FPolygon &&) = delete;
/// Deleted copy-assignment operator.
auto operator=(const FPolygon &) -> FPolygon & = delete;
/// Deleted move-assignment operator.
auto operator=(FPolygon &&) -> FPolygon & = delete;
/// Draws this FPolygon.
auto draw() const -> void { Mesh.draw(GL_TRIANGLE_FAN); }
/// Gets a reference to the underlying FMesh of this FPolygon.
/// @return Reference to the underlying FMesh of this FPolygon.
[[nodiscard]] auto getMesh() -> FMesh & { return Mesh; }
private:
/// FMesh describing the geometry of this FPolygon.
FMesh Mesh;
};
Usage
Using and initialization of the FPolygon
class looks as follows right now:
Polygon.getMesh().setVertexCount(3);
Polygon.getMesh().setVertex(
0, FVertex{glm::vec3{0, 0.5, 0}, glm::vec3{1, 0, 0}, glm::vec2{1}});
Polygon.getMesh().setVertex(
1, FVertex{glm::vec3{0.5, -0.5, 0}, glm::vec3{0, 1, 0}, glm::vec2{1}});
Polygon.getMesh().setVertex(
2, FVertex{glm::vec3{-0.5, -0.5, 0}, glm::vec3{0, 0, 1}, glm::vec2{1}});
Remarks
- The
FVertex
struct already has texture coordinates that are currently unused and I will add the handling later on. - No projections or transformation are currently set up, this is just about the very basics to get up and running.
Questions
- Right now all meshes would have their own vertex array object even though the data layout, based on
FVertex
, is identical between all of them. Is it common to use a shared vertex array object or is the overhead of duplicating them negligible? - Considering I want to create classes for sprites and tilemaps I was wondering whether it's better to inherit from
FMesh
or add it as a member variable. Is there anything I have to consider for this decision?