From c30872a4716be835d3e5fbdf4a5ab53b15bd0982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 8 Jan 2011 23:01:41 +0100 Subject: [PATCH] Class for building meshes from scratch or prefabricated data. --- src/MeshBuilder.h | 308 +++++++++++++++++++++++++++++++++++ src/Test/CMakeLists.txt | 1 + src/Test/MeshBuilderTest.cpp | 153 +++++++++++++++++ src/Test/MeshBuilderTest.h | 34 ++++ 4 files changed, 496 insertions(+) create mode 100644 src/MeshBuilder.h create mode 100644 src/Test/MeshBuilderTest.cpp create mode 100644 src/Test/MeshBuilderTest.h diff --git a/src/MeshBuilder.h b/src/MeshBuilder.h new file mode 100644 index 000000000..584f74f32 --- /dev/null +++ b/src/MeshBuilder.h @@ -0,0 +1,308 @@ +#ifndef Magnum_MeshBuilder_h +#define Magnum_MeshBuilder_h +/* + Copyright © 2010 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +/** @file + * @brief Class Magnum::MeshBuilder + */ + +#include +#include + +#include "Buffer.h" +#include "IndexedMesh.h" + +namespace Magnum { + +/** +@brief Mesh builder + +Class for building meshes with triangle primitive from scratch or from +prefabricated data and modifying them (adding/removing faces, cleaning duplicate +vertices etc.). + +Vertex template can be absolutely anything (single integer, structure, class) +but it must have at least operator== implemented. +*/ +template class MeshBuilder { + public: + /** @brief Triangle face */ + struct Face { + /** @brief Implicit constructor */ + Face() {} + + /** @brief Constructor */ + Face(Vertex* first, Vertex* second, Vertex* third) { + vertices[0] = first; + vertices[1] = second; + vertices[2] = third; + } + + /** @brief Vertex data */ + Vertex* vertices[3]; + }; + + /** + * @brief Destructor + * + * Removed all vertices and faces. + */ + inline virtual ~MeshBuilder() { clear(); } + + /** + * @brief Clear mesh data + * + * Should be called after the mesh is built and the builder is not + * needed. This function is also automatically called in destructor or + * when calling setData(). + */ + void clear() { + for(typename std::set::iterator it = _vertices.begin(); it != _vertices.end(); ++it) + delete *it; + + for(typename std::set::iterator it = _faces.begin(); it != _faces.end(); ++it) + delete *it; + + _vertices.clear(); + _faces.clear(); + vertexFaces.clear(); + } + + /** @brief Array with vertices */ + inline const std::set& vertices() const { return _vertices; } + + /** @brief Array with faces */ + inline const std::set& faces() const { return _faces; } + + /** + * @brief Set mesh data + * @param vertices Vertices + * @param indices Vertex indices + * @param vertexCount Vertex count + * @param indexCount Index count + * + * Replaces mesh builder data with given data. Type of indices is + * detected from given pointer. + */ + inline void setData(const Vertex* vertices, const GLubyte* indices, GLsizei vertexCount, GLsizei indexCount) { + setData(vertices, indices, vertexCount, indexCount, GL_UNSIGNED_BYTE); + } + + /** @copydoc setData() */ + inline void setData(const Vertex* vertices, const GLushort* indices, GLsizei vertexCount, GLsizei indexCount) { + setData(vertices, indices, vertexCount, indexCount, GL_UNSIGNED_SHORT); + } + + /** @copydoc setData() */ + inline void setData(const Vertex* vertices, const GLuint* indices, GLsizei vertexCount, GLsizei indexCount) { + setData(vertices, indices, vertexCount, indexCount, GL_UNSIGNED_INT); + } + + /** + * @brief Remove face from the mesh + * @param face Pointer to face which to remove + * + * If face vertices are not shared with another face, they are removed + * too. + */ + void removeFace(Face* face) { + /* Remove face from vertexFaces */ + for(int i = 0; i != 3; ++i) { + typename std::multimap::iterator it = vertexFaces.lower_bound(face->vertices[i]); + typename std::multimap::iterator end = vertexFaces.upper_bound(face->vertices[i]); + for(; it != end; ++it) if(it->second == face) { + vertexFaces.erase(it); + break; + } + + /* If the vertex is not part of any face anymore, remove it too */ + if(vertexFaces.count(face->vertices[i]) == 0) { + _vertices.erase(face->vertices[i]); + delete face->vertices[i]; + } + } + + /* Remove face from faces and delete it */ + _faces.erase(face); + delete face; + } + + /** + * @brief Add face + * @param face Pointer to face which to add + */ + void addFace(Face* face) { + /* Don't insert the same face twice */ + if(_faces.find(face) != _faces.end()) return; + + /* Add vertex to vertexData, if it is not yet there */ + for(int i = 0; i != 3; ++i) + _vertices.insert(face->vertices[i]); + + /* Add vertices to vertexFaces */ + for(int i = 0; i != 3; ++i) + vertexFaces.insert(std::pair(face->vertices[i], face)); + + /* Add face to face list */ + _faces.insert(face); + } + + /** + * @brief Clean the mesh + * + * Removes duplicate vertices from the mesh. + */ + void cleanMesh() { + /* Vertices scheduled for deletion */ + std::vector trashcan; + + /* Foreach all vertices and for each find similar in the rest of the array */ + for(typename std::set::iterator it = _vertices.begin(); it != _vertices.end(); ++it) { + typename std::set::iterator next = it; + for(typename std::set::iterator similarIt = ++next; similarIt != _vertices.end(); ++similarIt) { + if(**it == **similarIt) { + /* Range of faces sharing that similar vertex */ + typename std::multimap::iterator begin = vertexFaces.lower_bound(*similarIt); + typename std::multimap::iterator end = vertexFaces.upper_bound(*similarIt); + + /* Updated array of faces, now sharing the original vertex */ + std::multimap updatedFaces; + + /* Replace similar vertex in faces with this */ + for(typename std::multimap::iterator faceIt = begin; faceIt != end; ++faceIt) { + for(int i = 0; i != 3; ++i) if(*similarIt == faceIt->second->vertices[i]) { + faceIt->second->vertices[i] = *it; + } + + updatedFaces.insert(std::pair(*it, faceIt->second)); + } + + /* Remove old faces, insert updated */ + vertexFaces.erase(begin, end); + vertexFaces.insert(updatedFaces.begin(), updatedFaces.end()); + + /* Schedule vertex for deletion */ + trashcan.push_back(*similarIt); + } + } + } + + /* Delete all scheduled vertices */ + for(typename std::vector::const_iterator it = trashcan.begin(); it != trashcan.end(); ++it) { + _vertices.erase(*it); + delete *it; + } + } + + /** + * @brief Build indexed mesh and fill existing buffers with it + * @param mesh Mesh. The mesh primitive is set to + * Mesh::Triangles, if it is not already, vertex and index count + * is updated to values from the builder. + * @param vertexBuffer Vertex buffer created as interleaved + * with Mesh::addBuffer(). Otherwise the behaviour is undefined. + * @param vertexBufferUsage Usage of the vertex buffer. + * @param indexBufferUsage Usage of the index buffer. + * + * Builds indexed mesh from the data and fills given mesh buffers with + * them. + * @note The mesh is @b not cleaned before building. + */ + void build(IndexedMesh* mesh, Buffer* vertexBuffer, Buffer::Usage vertexBufferUsage, Buffer::Usage indexBufferUsage) { + if(_faces.size()*3 <= 0xFF) + buildInternal(mesh, vertexBuffer, vertexBufferUsage, indexBufferUsage, GL_UNSIGNED_BYTE); + else if(_faces.size()*3 <= 0xFFFF) + buildInternal(mesh, vertexBuffer, vertexBufferUsage, indexBufferUsage, GL_UNSIGNED_SHORT); + else + buildInternal(mesh, vertexBuffer, vertexBufferUsage, indexBufferUsage, GL_UNSIGNED_INT); + } + + /** + * @brief Build indexed data and create new mesh from them + * @param vertexBufferUsage Usage of the vertex buffer. + * @param indexBufferUsage Usage of the index buffer. + * + * @see build(IndexedMesh*, Buffer*, Buffer::Usage, Buffer::Usage) + */ + IndexedMesh* build(Buffer::Usage vertexBufferUsage, Buffer::Usage indexBufferUsage) { + IndexedMesh mesh = new IndexedMesh(Mesh::Triangles, 0, 0, GL_UNSIGNED_BYTE); + Buffer* vertexBuffer = mesh.addBuffer(true); + + build(mesh, vertexBuffer, vertexBufferUsage, indexBufferUsage); + return mesh; + } + + private: + std::set _vertices; + std::multimap vertexFaces; + std::set _faces; + + template void setData(const Vertex* vertexData, const IndexType* indices, GLsizei vertexCount, GLsizei indexCount, GLenum indexType) { + clear(); + + /* Map vertex indices to vertex pointers */ + std::map vertexMapping; + for(IndexType i = 0; i != vertexCount; ++i) { + Vertex* v = new Vertex(vertexData[i]); + _vertices.insert(v); + vertexMapping.insert(std::pair(i, v)); + } + + /* Faces array */ + for(IndexType i = 0; i < indexCount; i += 3) { + Face* f = new Face; + for(int ii = 0; ii != 3; ++ii) { + f->vertices[ii] = vertexMapping[indices[i+ii]]; + vertexFaces.insert(std::pair(f->vertices[ii], f)); + } + _faces.insert(f); + } + } + + template void buildInternal(IndexedMesh* mesh, Buffer* vertexBuffer, Buffer::Usage vertexBufferUsage, Buffer::Usage indexBufferUsage, GLenum indexType) { + /* Update the mesh parameters */ + mesh->setPrimitive(Mesh::Triangles); + mesh->setVertexCount(_vertices.size()); + mesh->setIndexCount(_faces.size()*3); + mesh->setIndexType(indexType); + + /* Convert vertex pointers to fixed vector and map vertex data + pointers to vertex indices */ + std::vector vertices; + std::map indicesMapping; + IndexType i = 0; + for(typename std::set::const_iterator it = _vertices.begin(); it != _vertices.end(); ++it) { + vertices.push_back(**it); + indicesMapping.insert(std::pair(*it, i++)); + } + + /* Create indices array */ + std::vector indices; + indices.reserve(_faces.size()*3); + for(typename std::set::const_iterator it = _faces.begin(); it != _faces.end(); ++it) { + for(int i = 0; i != 3; ++i) + indices.push_back(indicesMapping[(*it)->vertices[i]]); + } + + /* Create mesh */ + vertexBuffer->setData(sizeof(Vertex)*vertices.size(), vertices.data(), vertexBufferUsage); + mesh->indexBuffer()->setData(sizeof(IndexType)*indices.size(), indices.data(), indexBufferUsage); + } +}; + +} + +#endif diff --git a/src/Test/CMakeLists.txt b/src/Test/CMakeLists.txt index f42e0fa06..0320aa580 100644 --- a/src/Test/CMakeLists.txt +++ b/src/Test/CMakeLists.txt @@ -1,3 +1,4 @@ magnum_add_test(ObjectTest ObjectTest.h ObjectTest.cpp Magnum) magnum_add_test(CameraTest CameraTest.h CameraTest.cpp Magnum) magnum_add_test(SceneTest SceneTest.h SceneTest.cpp Magnum) +magnum_add_test(MeshBuilderTest MeshBuilderTest.h MeshBuilderTest.cpp Magnum) diff --git a/src/Test/MeshBuilderTest.cpp b/src/Test/MeshBuilderTest.cpp new file mode 100644 index 000000000..9c7010fa4 --- /dev/null +++ b/src/Test/MeshBuilderTest.cpp @@ -0,0 +1,153 @@ +/* + Copyright © 2010, 2011 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include "MeshBuilderTest.h" + +#include +#include + +#include "MeshBuilder.h" + +QTEST_APPLESS_MAIN(Magnum::Test::MeshBuilderTest) + +using namespace std; + +namespace Magnum { namespace Test { + +void MeshBuilderTest::setData() { + MeshBuilder builder; + + int vertexData[] = { 1, 2, 3, 4 }; + GLubyte indices[] = { 0, 1, 2, 1, 2, 3 }; + builder.setData(vertexData, indices, 4, 6); + + set vertices = builder.vertices(); + QVERIFY(vertices.size() == 4); + + int i = 1; + for(set::const_iterator it = vertices.begin(); it != vertices.end(); ++it) + QVERIFY(**it == i++); + + set::Face*> faces = builder.faces(); + QVERIFY(faces.size() == 2); + + set::Face*>::const_iterator it = faces.begin(); + QVERIFY(*(*it)->vertices[0] == 1); + QVERIFY(*(*it)->vertices[1] == 2); + QVERIFY(*(*it)->vertices[2] == 3); + + it++; + QVERIFY(*(*it)->vertices[0] == 2); + QVERIFY(*(*it)->vertices[1] == 3); + QVERIFY(*(*it)->vertices[2] == 4); +} + +void MeshBuilderTest::addFace() { + MeshBuilder builder; + + int* v1 = new int(1); + int* v2 = new int(2); + int* v3 = new int(3); + int* v4 = new int(4); + + MeshBuilder::Face* f1 = new MeshBuilder::Face(v1, v2, v3); + builder.addFace(f1); + MeshBuilder::Face* f2 = new MeshBuilder::Face(v2, v3, v4); + builder.addFace(f2); + + set vertices; + vertices.insert(v1); + vertices.insert(v2); + vertices.insert(v3); + vertices.insert(v4); + + set::Face*> faces; + faces.insert(f1); + faces.insert(f2); + + QVERIFY(builder.vertices() == vertices); + QVERIFY(builder.faces() == faces); +} + +void MeshBuilderTest::removeFace() { + MeshBuilder builder; + + int* v1 = new int(1); + int* v2 = new int(2); + int* v3 = new int(3); + int* v4 = new int(4); + + MeshBuilder::Face* f1 = new MeshBuilder::Face(v1, v2, v3); + builder.addFace(f1); + MeshBuilder::Face* f2 = new MeshBuilder::Face(v2, v3, v4); + builder.addFace(f2); + MeshBuilder::Face* f3 = new MeshBuilder::Face(v2, v3, v1); + builder.addFace(f3); + + /* Remove second face */ + builder.removeFace(f2); + + set vertices; + vertices.insert(v1); + vertices.insert(v2); + vertices.insert(v3); + + set::Face*> faces; + faces.insert(f1); + faces.insert(f3); + + QVERIFY(builder.vertices() == vertices); + QVERIFY(builder.faces() == faces); + + /* Remove third face (vertices shouldn't change) */ + builder.removeFace(f3); + faces.erase(f3); + QVERIFY(builder.vertices() == vertices); + QVERIFY(builder.faces() == faces); +} + +void MeshBuilderTest::cleanMesh() { + MeshBuilder builder; + + int* v1 = new int(1); + int* v2 = new int(2); + int* v3 = new int(1); + int* v4 = new int(4); + + MeshBuilder::Face* f1 = new MeshBuilder::Face(v1, v2, v3); + builder.addFace(f1); + MeshBuilder::Face* f2 = new MeshBuilder::Face(v2, v3, v4); + builder.addFace(f2); + + builder.cleanMesh(); + int* unique = (f1->vertices[2] == v1) ? v1 : v3; + + set vertices; + vertices.insert(unique); + vertices.insert(v2); + vertices.insert(v4); + + set::Face*> faces; + faces.insert(f1); + faces.insert(f2); + + /* Verify cleanup */ + QVERIFY(f1->vertices[2] == unique); + QVERIFY(f2->vertices[1] == unique); + QVERIFY(builder.vertices() == vertices); + QVERIFY(builder.faces() == faces); +} + +}} diff --git a/src/Test/MeshBuilderTest.h b/src/Test/MeshBuilderTest.h new file mode 100644 index 000000000..85635c499 --- /dev/null +++ b/src/Test/MeshBuilderTest.h @@ -0,0 +1,34 @@ +#ifndef Magnum_Test_MeshBuilderTest_h +#define Magnum_Test_MeshBuilderTest_h +/* + Copyright © 2010, 2011 Vladimír Vondruš + + This file is part of Magnum. + + Magnum is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License version 3 + only, as published by the Free Software Foundation. + + Magnum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License version 3 for more details. +*/ + +#include + +namespace Magnum { namespace Test { + +class MeshBuilderTest: public QObject { + Q_OBJECT + + private slots: + void setData(); + void addFace(); + void removeFace(); + void cleanMesh(); +}; + +}} + +#endif