diff --git a/examples/common/OpenGLWidget.cpp b/examples/common/OpenGLWidget.cpp index 439264bf..44747f2b 100644 --- a/examples/common/OpenGLWidget.cpp +++ b/examples/common/OpenGLWidget.cpp @@ -161,7 +161,7 @@ void OpenGLWidget::paintGL() // Bind the vertex array for the current mesh glBindVertexArray(meshData.vertexArrayObject); // Draw the mesh - glDrawElements(GL_TRIANGLES, meshData.noOfIndices, GL_UNSIGNED_INT, 0); + glDrawElements(GL_TRIANGLES, meshData.noOfIndices, meshData.indexType, 0); // Unbind the vertex array. glBindVertexArray(0); } diff --git a/examples/common/OpenGLWidget.h b/examples/common/OpenGLWidget.h index 1bbbc1e4..5ceb28ae 100644 --- a/examples/common/OpenGLWidget.h +++ b/examples/common/OpenGLWidget.h @@ -36,6 +36,7 @@ distribution. struct OpenGLMeshData { GLuint noOfIndices; + GLenum indexType; GLuint indexBuffer; GLuint vertexBuffer; GLuint vertexArrayObject; @@ -53,8 +54,8 @@ public: OpenGLWidget(QWidget *parent); // Convert a PolyVox mesh to OpenGL index/vertex buffers. Inlined because it's templatised. - template - void addMesh(const PolyVox::Mesh< PolyVox::Vertex< DataType > >& surfaceMesh, const PolyVox::Vector3DInt32& translation = PolyVox::Vector3DInt32(0, 0, 0), float scale = 1.0f) + template + void addMesh(const MeshType& surfaceMesh, const PolyVox::Vector3DInt32& translation = PolyVox::Vector3DInt32(0, 0, 0), float scale = 1.0f) { // Convienient access to the vertices and indices const auto& vecIndices = surfaceMesh.getIndices(); @@ -71,29 +72,29 @@ public: // The GL_ARRAY_BUFFER will contain the list of vertex positions glGenBuffers(1, &(meshData.vertexBuffer)); glBindBuffer(GL_ARRAY_BUFFER, meshData.vertexBuffer); - glBufferData(GL_ARRAY_BUFFER, vecVertices.size() * sizeof(PolyVox::Vertex< DataType >), vecVertices.data(), GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, vecVertices.size() * sizeof(typename MeshType::VertexType), vecVertices.data(), GL_STATIC_DRAW); // and GL_ELEMENT_ARRAY_BUFFER will contain the indices glGenBuffers(1, &(meshData.indexBuffer)); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshData.indexBuffer); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, vecIndices.size() * sizeof(uint32_t), vecIndices.data(), GL_STATIC_DRAW); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, vecIndices.size() * sizeof(typename MeshType::IndexType), vecIndices.data(), GL_STATIC_DRAW); // Every surface extractor outputs valid positions for the vertices, so tell OpenGL how these are laid out glEnableVertexAttribArray(0); // Attrib '0' is the vertex positions - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(PolyVox::Vertex< DataType >), (GLvoid*)(offsetof(PolyVox::Vertex< DataType >, position))); //take the first 3 floats from every sizeof(decltype(vecVertices)::value_type) + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(typename MeshType::VertexType), (GLvoid*)(offsetof(typename MeshType::VertexType, position))); //take the first 3 floats from every sizeof(decltype(vecVertices)::value_type) // Some surface extractors also generate normals, so tell OpenGL how these are laid out. If a surface extractor // does not generate normals then nonsense values are written into the buffer here and sghould be ignored by the // shader. This is mostly just to simplify this example code - in a real application you will know whether your // chosen surface extractor generates normals and can skip uploading them if not. glEnableVertexAttribArray(1); // Attrib '1' is the vertex normals. - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(PolyVox::Vertex< DataType >), (GLvoid*)(offsetof(PolyVox::Vertex< DataType >, normal))); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(typename MeshType::VertexType), (GLvoid*)(offsetof(typename MeshType::VertexType, normal))); // Finally a surface extractor will probably output additional data. This is highly application dependant. For this example code // we're just uploading it as a set of bytes which we can read individually, but real code will want to do something specialised here. glEnableVertexAttribArray(2); //We're talking about shader attribute '2' - GLint size = (std::min)(sizeof(DataType), size_t(4)); // Can't upload more that 4 components (vec4 is GLSL's biggest type) - glVertexAttribIPointer(2, size, GL_UNSIGNED_BYTE, sizeof(PolyVox::Vertex< DataType >), (GLvoid*)(offsetof(PolyVox::Vertex< DataType >, data))); + GLint size = (std::min)(sizeof(typename MeshType::VertexType::DataType), size_t(4)); // Can't upload more that 4 components (vec4 is GLSL's biggest type) + glVertexAttribIPointer(2, size, GL_UNSIGNED_BYTE, sizeof(typename MeshType::VertexType), (GLvoid*)(offsetof(typename MeshType::VertexType, data))); // We're done uploading and can now unbind. glBindVertexArray(0); @@ -103,6 +104,9 @@ public: meshData.translation = QVector3D(translation.getX(), translation.getY(), translation.getZ()); meshData.scale = scale; + // Set 16 or 32-bit index buffer size. + meshData.indexType = sizeof(typename MeshType::IndexType) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT; + // Now add the mesh to the list of meshes to render. addMeshData(meshData); } diff --git a/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.h b/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.h index 5e059e10..409ed096 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.h +++ b/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.h @@ -53,6 +53,11 @@ namespace PolyVox DataType data; }; + // Convienient shorthand for declaring a mesh of 'cubic' vertices + // Currently disabled because it requires GCC 4.7 + //template + //using CubicMesh = Mesh< CubicVertex, IndexType >; + /// Decodes a position from a CubicVertex inline Vector3DFloat decodePosition(const Vector3DUint8& encodedPosition) { @@ -71,6 +76,102 @@ namespace PolyVox result.data = cubicVertex.data; // Data is not encoded return result; } + + /// Do not use this class directly. Use the 'extractCubicSurface' function instead (see examples). + template + class CubicSurfaceExtractor + { + struct IndexAndMaterial + { + int32_t iIndex; + typename VolumeType::VoxelType uMaterial; + }; + + enum FaceNames + { + PositiveX, + PositiveY, + PositiveZ, + NegativeX, + NegativeY, + NegativeZ, + NoOfFaces + }; + + struct Quad + { + Quad(uint32_t v0, uint32_t v1, uint32_t v2, uint32_t v3) + { + vertices[0] = v0; + vertices[1] = v1; + vertices[2] = v2; + vertices[3] = v3; + } + + uint32_t vertices[4]; + }; + + public: + CubicSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, IsQuadNeeded isQuadNeeded = IsQuadNeeded(), WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType(), bool bMergeQuads = true); + + void execute(); + + private: + int32_t addVertex(uint32_t uX, uint32_t uY, uint32_t uZ, typename VolumeType::VoxelType uMaterial, Array<3, IndexAndMaterial>& existingVertices); + bool performQuadMerging(std::list& quads); + bool mergeQuads(Quad& q1, Quad& q2); + + IsQuadNeeded m_funcIsQuadNeededCallback; + + //The volume data and a sampler to access it. + VolumeType* m_volData; + + //Information about the region we are currently processing + Region m_regSizeInVoxels; + + //The surface patch we are currently filling. + MeshType* m_meshCurrent; + + //Used to avoid creating duplicate vertices. + Array<3, IndexAndMaterial> m_previousSliceVertices; + Array<3, IndexAndMaterial> m_currentSliceVertices; + + //During extraction we create a number of different lists of quads. All the + //quads in a given list are in the same plane and facing in the same direction. + std::vector< std::list > m_vecQuads[NoOfFaces]; + + //Controls whether quad merging should be performed. This might be undesirable + //is the user needs per-vertex attributes, or to perform per vertex lighting. + bool m_bMergeQuads; + + //This constant defines the maximum number of quads which can share a + //vertex in a cubic style mesh. See the initialisation for more details. + static const uint32_t MaxVerticesPerPosition; + + //The wrap mode + WrapMode m_eWrapMode; + typename VolumeType::VoxelType m_tBorderValue; + }; + + // This version of the function performs the extraction into a user-provided mesh rather than allocating a mesh automatically. + // There are a few reasons why this might be useful to more advanced users: + // + // 1. It leaves the user in control of memory allocation and would allow them to implement e.g. a mesh pooling system. + // 2. The user-provided mesh could have a different index type (e.g. 16-bit indices) to reduce memory usage. + // 3. The user could provide a custom mesh class, e.g a thin wrapper around an openGL VBO to allow direct writing into this structure. + // + // We don't provide a default MeshType here. If the user doesn't want to provide a MeshType then it probably makes + // more sense to use the other variant of this function where the mesh is a return value rather than a parameter. + // + // Note: This function is called 'extractCubicMeshCustom' rather than 'extractCubicMesh' to avoid ambiguity when only three parameters + // are provided (would the third parameter be a controller or a mesh?). It seems this can be fixed by using enable_if/static_assert to emulate concepts, + // but this is relatively complex and I haven't done it yet. Could always add it later as another overload. + template > + void extractCubicMeshCustom(VolumeType* volData, Region region, MeshType* result, IsQuadNeeded isQuadNeeded = IsQuadNeeded(), WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType(), bool bMergeQuads = true) + { + CubicSurfaceExtractor extractor(volData, region, result, isQuadNeeded, eWrapMode, tBorderValue, bMergeQuads); + extractor.execute(); + } /// The CubicSurfaceExtractor creates a mesh in which each voxel appears to be rendered as a cube //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -113,110 +214,13 @@ namespace PolyVox /// /// Another scenario which sometimes results in confusion is when you wish to extract a region which corresponds to the whole volume, partcularly when solid voxels extend right to the edge of the volume. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - template - class CubicSurfaceExtractor + template > + Mesh > extractCubicMesh(VolumeType* volData, Region region, IsQuadNeeded isQuadNeeded = IsQuadNeeded(), WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType(), bool bMergeQuads = true) { - struct IndexAndMaterial - { - int32_t iIndex; - typename VolumeType::VoxelType uMaterial; - }; - - enum FaceNames - { - PositiveX, - PositiveY, - PositiveZ, - NegativeX, - NegativeY, - NegativeZ, - NoOfFaces - }; - - struct Quad - { - Quad(uint32_t v0, uint32_t v1, uint32_t v2, uint32_t v3) - { - vertices[0] = v0; - vertices[1] = v1; - vertices[2] = v2; - vertices[3] = v3; - } - - uint32_t vertices[4]; - }; - - public: - // This is a bit ugly - it seems that the C++03 syntax is different from the C++11 syntax? See this thread: http://stackoverflow.com/questions/6076015/typename-outside-of-template - // Long term we should probably come back to this and if the #ifdef is still needed then maybe it should check for C++11 mode instead of MSVC? -#if defined(_MSC_VER) - CubicSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = VolumeType::VoxelType(), bool bMergeQuads = true, IsQuadNeeded isQuadNeeded = IsQuadNeeded()); -#else - CubicSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType(), bool bMergeQuads = true, IsQuadNeeded isQuadNeeded = IsQuadNeeded()); -#endif - - - void execute(); - - private: - int32_t addVertex(uint32_t uX, uint32_t uY, uint32_t uZ, typename VolumeType::VoxelType uMaterial, Array<3, IndexAndMaterial>& existingVertices); - bool performQuadMerging(std::list& quads); - bool mergeQuads(Quad& q1, Quad& q2); - - IsQuadNeeded m_funcIsQuadNeededCallback; - - //The volume data and a sampler to access it. - VolumeType* m_volData; - - //Information about the region we are currently processing - Region m_regSizeInVoxels; - - //The surface patch we are currently filling. - MeshType* m_meshCurrent; - - //Used to avoid creating duplicate vertices. - Array<3, IndexAndMaterial> m_previousSliceVertices; - Array<3, IndexAndMaterial> m_currentSliceVertices; - - //During extraction we create a number of different lists of quads. All the - //quads in a given list are in the same plane and facing in the same direction. - std::vector< std::list > m_vecQuads[NoOfFaces]; - - //Controls whether quad merging should be performed. This might be undesirable - //is the user needs per-vertex attributes, or to perform per vertex lighting. - bool m_bMergeQuads; - - //This constant defines the maximum number of quads which can share a - //vertex in a cubic style mesh. See the initialisation for more details. - static const uint32_t MaxVerticesPerPosition; - - //The wrap mode - WrapMode m_eWrapMode; - typename VolumeType::VoxelType m_tBorderValue; - }; - - template - Mesh > extractCubicMesh(VolumeType* volData, Region region, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, bool bMergeQuads, IsQuadNeeded isQuadNeeded) - { - typedef Mesh > MeshType; - MeshType result; - CubicSurfaceExtractor extractor(volData, region, &result, eWrapMode, tBorderValue, bMergeQuads, isQuadNeeded); - extractor.execute(); + Mesh< CubicVertex > result; + extractCubicMeshCustom(volData, region, &result, isQuadNeeded, eWrapMode, tBorderValue, bMergeQuads); return result; } - - template - // This is a bit ugly - it seems that the C++03 syntax is different from the C++11 syntax? See this thread: http://stackoverflow.com/questions/6076015/typename-outside-of-template - // Long term we should probably come back to this and if the #ifdef is still needed then maybe it should check for C++11 mode instead of MSVC? -#if defined(_MSC_VER) - Mesh > extractCubicMesh(VolumeType* volData, Region region, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = VolumeType::VoxelType(), bool bMergeQuads = true) -#else - Mesh > extractCubicMesh(VolumeType* volData, Region region, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType(), bool bMergeQuads = true) -#endif - { - DefaultIsQuadNeeded isQuadNeeded; - return extractCubicMesh >(volData, region, eWrapMode, tBorderValue, bMergeQuads, isQuadNeeded); - } } #include "PolyVoxCore/CubicSurfaceExtractor.inl" diff --git a/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.inl b/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.inl index 6ebec5ef..edf8ea7a 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.inl +++ b/library/PolyVoxCore/include/PolyVoxCore/CubicSurfaceExtractor.inl @@ -36,7 +36,7 @@ namespace PolyVox const uint32_t CubicSurfaceExtractor::MaxVerticesPerPosition = 8; template - CubicSurfaceExtractor::CubicSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, bool bMergeQuads, IsQuadNeeded isQuadNeeded) + CubicSurfaceExtractor::CubicSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, IsQuadNeeded isQuadNeeded, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, bool bMergeQuads) :m_volData(volData) ,m_regSizeInVoxels(region) ,m_meshCurrent(result) diff --git a/library/PolyVoxCore/include/PolyVoxCore/DefaultMarchingCubesController.h b/library/PolyVoxCore/include/PolyVoxCore/DefaultMarchingCubesController.h index 6a7aaa22..2d1b6144 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/DefaultMarchingCubesController.h +++ b/library/PolyVoxCore/include/PolyVoxCore/DefaultMarchingCubesController.h @@ -75,8 +75,15 @@ namespace PolyVox * if the voxel type is 'float' then the representable range is -FLT_MAX to FLT_MAX and the threshold will be set to zero. */ DefaultMarchingCubesController(void) - :m_tThreshold(((std::numeric_limits::min)() + (std::numeric_limits::max)()) / 2) - { + { + if (std::is_signed()) + { + m_tThreshold = DensityType(0); + } + else + { + m_tThreshold = (((std::numeric_limits::min)() + (std::numeric_limits::max)()) / 2); + } } /** diff --git a/library/PolyVoxCore/include/PolyVoxCore/Impl/TypeDef.h b/library/PolyVoxCore/include/PolyVoxCore/Impl/TypeDef.h index 1069a791..f9ac641e 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/Impl/TypeDef.h +++ b/library/PolyVoxCore/include/PolyVoxCore/Impl/TypeDef.h @@ -24,6 +24,10 @@ freely, subject to the following restrictions: #ifndef __PolyVox_TypeDef_H__ #define __PolyVox_TypeDef_H__ +#if defined(_MSC_VER) && (_MSC_VER < 1800) +#error "Your version of Visual Studio is too old to build PolyVox. You need at least version Visual Stusio 2013" +#endif + //Definitions needed to make library functions accessable // See http://gcc.gnu.org/wiki/Visibility for more info. #if defined _WIN32 || defined __CYGWIN__ diff --git a/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h b/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h index 8f73a6cf..cf42800e 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h +++ b/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.h @@ -55,6 +55,11 @@ namespace PolyVox DataType data; }; + // Convienient shorthand for declaring a mesh of marching cubes vertices + // Currently disabled because it requires GCC 4.7 + //template + //using MarchingCubesMesh = Mesh< MarchingCubesVertex, IndexType >; + /// Decodes a position from a MarchingCubesVertex inline Vector3DFloat decodePosition(const Vector3DUint16& encodedPosition) { @@ -90,8 +95,8 @@ namespace PolyVox // floats into two bytes and store them in a single uint16_t // Move from range [-1.0f, 1.0f] to [0.0f, 255.0f] - px = (px + 1.0) * 127.5f; - py = (py + 1.0) * 127.5f; + px = (px + 1.0f) * 127.5f; + py = (py + 1.0f) * 127.5f; // Convert to uints uint16_t resultX = static_cast(px + 0.5f); @@ -147,17 +152,12 @@ namespace PolyVox return result; } - template< typename VolumeType, typename Controller = DefaultMarchingCubesController > + /// Do not use this class directly. Use the 'extractMarchingCubesSurface' function instead (see examples). + template< typename VolumeType, typename MeshType, typename ControllerType> class MarchingCubesSurfaceExtractor { public: - // This is a bit ugly - it seems that the C++03 syntax is different from the C++11 syntax? See this thread: http://stackoverflow.com/questions/6076015/typename-outside-of-template - // Long term we should probably come back to this and if the #ifdef is still needed then maybe it should check for C++11 mode instead of MSVC? -#if defined(_MSC_VER) - MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, Mesh >* result, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = VolumeType::VoxelType(), Controller controller = Controller()); -#else - MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, Mesh >* result, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType(), Controller controller = Controller()); -#endif + MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, ControllerType controller, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType()); void execute(); @@ -306,7 +306,7 @@ namespace PolyVox uint32_t m_uNoOfOccupiedCells; //The surface patch we are currently filling. - Mesh >* m_meshCurrent; + MeshType* m_meshCurrent; //Information about the region we are currently processing Region m_regSizeInVoxels; @@ -318,32 +318,38 @@ namespace PolyVox Region m_regSliceCurrent; //Used to convert arbitrary voxel types in densities and materials. - Controller m_controller; + ControllerType m_controller; //Our threshold value - typename Controller::DensityType m_tThreshold; + typename ControllerType::DensityType m_tThreshold; }; - template< typename VolumeType, typename Controller> - Mesh > extractMarchingCubesMesh(VolumeType* volData, Region region, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, Controller controller) + // This version of the function performs the extraction into a user-provided mesh rather than allocating a mesh automatically. + // There are a few reasons why this might be useful to more advanced users: + // + // 1. It leaves the user in control of memory allocation and would allow them to implement e.g. a mesh pooling system. + // 2. The user-provided mesh could have a different index type (e.g. 16-bit indices) to reduce memory usage. + // 3. The user could provide a custom mesh class, e.g a thin wrapper around an openGL VBO to allow direct writing into this structure. + // + // We don't provide a default MeshType here. If the user doesn't want to provide a MeshType then it probably makes + // more sense to use the other variant of this function where the mesh is a return value rather than a parameter. + // + // Note: This function is called 'extractMarchingCubesMeshCustom' rather than 'extractMarchingCubesMesh' to avoid ambiguity when only three parameters + // are provided (would the third parameter be a controller or a mesh?). It seems this can be fixed by using enable_if/static_assert to emulate concepts, + // but this is relatively complex and I haven't done it yet. Could always add it later as another overload. + template< typename VolumeType, typename MeshType, typename ControllerType = DefaultMarchingCubesController > + void extractMarchingCubesMeshCustom(VolumeType* volData, Region region, MeshType* result, ControllerType controller = ControllerType(), WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType()) { - Mesh > result; - MarchingCubesSurfaceExtractor extractor(volData, region, &result, eWrapMode, tBorderValue, controller); + MarchingCubesSurfaceExtractor extractor(volData, region, result, controller, eWrapMode, tBorderValue); extractor.execute(); - return result; } - template< typename VolumeType> - // This is a bit ugly - it seems that the C++03 syntax is different from the C++11 syntax? See this thread: http://stackoverflow.com/questions/6076015/typename-outside-of-template - // Long term we should probably come back to this and if the #ifdef is still needed then maybe it should check for C++11 mode instead of MSVC? -#if defined(_MSC_VER) - Mesh > extractMarchingCubesMesh(VolumeType* volData, Region region, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = VolumeType::VoxelType()) -#else - Mesh > extractMarchingCubesMesh(VolumeType* volData, Region region, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType()) -#endif + template< typename VolumeType, typename ControllerType = DefaultMarchingCubesController > + Mesh > extractMarchingCubesMesh(VolumeType* volData, Region region, ControllerType controller = ControllerType(), WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType()) { - DefaultMarchingCubesController controller; - return extractMarchingCubesMesh(volData, region, eWrapMode, tBorderValue, controller); + Mesh > result; + extractMarchingCubesMeshCustom, DefaultIndexType > >(volData, region, &result, controller, eWrapMode, tBorderValue); + return result; } } diff --git a/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.inl b/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.inl index cbbcd9e8..fd226560 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.inl +++ b/library/PolyVoxCore/include/PolyVoxCore/MarchingCubesSurfaceExtractor.inl @@ -25,8 +25,8 @@ freely, subject to the following restrictions: namespace PolyVox { - template - MarchingCubesSurfaceExtractor::MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, Mesh >* result, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, Controller controller) + template + MarchingCubesSurfaceExtractor::MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, ControllerType controller, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue) :m_volData(volData) ,m_sampVolume(volData) ,m_meshCurrent(result) @@ -34,6 +34,7 @@ namespace PolyVox ,m_controller(controller) ,m_tThreshold(m_controller.getThreshold()) { + POLYVOX_THROW_IF(m_meshCurrent == nullptr, std::invalid_argument, "Provided mesh cannot be null"); //m_regSizeInVoxels.cropTo(m_volData->getEnclosingRegion()); m_regSizeInCells = m_regSizeInVoxels; m_regSizeInCells.setUpperCorner(m_regSizeInCells.getUpperCorner() - Vector3DInt32(1,1,1)); @@ -41,8 +42,8 @@ namespace PolyVox m_sampVolume.setWrapMode(eWrapMode, tBorderValue); } - template - void MarchingCubesSurfaceExtractor::execute() + template + void MarchingCubesSurfaceExtractor::execute() { Timer timer; m_meshCurrent->clear(); @@ -129,9 +130,9 @@ namespace PolyVox << "x" << m_regSizeInVoxels.getDepthInVoxels() << ")"); } - template + template template - uint32_t MarchingCubesSurfaceExtractor::computeBitmaskForSlice(const Array2DUint8& pPreviousBitmask, Array2DUint8& pCurrentBitmask) + uint32_t MarchingCubesSurfaceExtractor::computeBitmaskForSlice(const Array2DUint8& pPreviousBitmask, Array2DUint8& pCurrentBitmask) { m_uNoOfOccupiedCells = 0; @@ -195,9 +196,9 @@ namespace PolyVox return m_uNoOfOccupiedCells; } - template + template template - void MarchingCubesSurfaceExtractor::computeBitmaskForCell(const Array2DUint8& pPreviousBitmask, Array2DUint8& pCurrentBitmask, uint32_t uXRegSpace, uint32_t uYRegSpace) + void MarchingCubesSurfaceExtractor::computeBitmaskForCell(const Array2DUint8& pPreviousBitmask, Array2DUint8& pCurrentBitmask, uint32_t uXRegSpace, uint32_t uYRegSpace) { uint8_t iCubeIndex = 0; @@ -395,8 +396,8 @@ namespace PolyVox } } - template - void MarchingCubesSurfaceExtractor::generateVerticesForSlice(const Array2DUint8& pCurrentBitmask, + template + void MarchingCubesSurfaceExtractor::generateVerticesForSlice(const Array2DUint8& pCurrentBitmask, Array2DInt32& m_pCurrentVertexIndicesX, Array2DInt32& m_pCurrentVertexIndicesY, Array2DInt32& m_pCurrentVertexIndicesZ) @@ -535,8 +536,8 @@ namespace PolyVox } } - template - void MarchingCubesSurfaceExtractor::generateIndicesForSlice(const Array2DUint8& pPreviousBitmask, + template + void MarchingCubesSurfaceExtractor::generateIndicesForSlice(const Array2DUint8& pPreviousBitmask, const Array2DInt32& m_pPreviousVertexIndicesX, const Array2DInt32& m_pPreviousVertexIndicesY, const Array2DInt32& m_pPreviousVertexIndicesZ, diff --git a/library/PolyVoxCore/include/PolyVoxCore/Mesh.h b/library/PolyVoxCore/include/PolyVoxCore/Mesh.h index 64356785..4c1b3582 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/Mesh.h +++ b/library/PolyVoxCore/include/PolyVoxCore/Mesh.h @@ -50,43 +50,51 @@ namespace PolyVox Mesh(); ~Mesh(); - const std::vector& getIndices(void) const; - uint32_t getNoOfIndices(void) const; IndexType getNoOfVertices(void) const; - const std::vector& getVertices(void) const; - const Vector3DInt32& getOffset(void) const; + const VertexType& getVertex(IndexType index) const; + const VertexType* getRawVertexData(void) const; + POLYVOX_DEPRECATED const std::vector& getVertices(void) const; + uint32_t getNoOfIndices(void) const; + IndexType getIndex(uint32_t index) const; + const IndexType* getRawIndexData(void); + POLYVOX_DEPRECATED const std::vector& getIndices(void) const; + + const Vector3DInt32& getOffset(void) const; void setOffset(const Vector3DInt32& offset); - void addTriangle(IndexType index0, IndexType index1, IndexType index2); IndexType addVertex(const VertexType& vertex); + void addTriangle(IndexType index0, IndexType index1, IndexType index2); + void clear(void); bool isEmpty(void) const; - void removeUnusedVertices(void); - - Vector3DInt32 m_offset; + void removeUnusedVertices(void); - public: - std::vector m_vecTriangleIndices; + private: + std::vector m_vecIndices; std::vector m_vecVertices; + Vector3DInt32 m_offset; }; template - Mesh< Vertex< typename MeshType::VertexType::DataType >, typename MeshType::IndexType > decodeMesh(const MeshType& mesh) + Mesh< Vertex< typename MeshType::VertexType::DataType >, typename MeshType::IndexType > decodeMesh(const MeshType& encodedMesh) { - Mesh< Vertex< typename MeshType::VertexType::DataType >, typename MeshType::IndexType > result; - result.m_vecVertices.resize(mesh.m_vecVertices.size()); + Mesh< Vertex< typename MeshType::VertexType::DataType >, typename MeshType::IndexType > decodedMesh; - for(typename MeshType::IndexType ct = 0; ct < mesh.m_vecVertices.size(); ct++) + for (typename MeshType::IndexType ct = 0; ct < encodedMesh.getNoOfVertices(); ct++) { - result.m_vecVertices[ct] = decodeVertex(mesh.m_vecVertices[ct]); + decodedMesh.addVertex(decodeVertex(encodedMesh.getVertex(ct))); } - result.m_vecTriangleIndices = mesh.m_vecTriangleIndices; + POLYVOX_ASSERT(encodedMesh.getNoOfIndices() % 3 == 0, "The number of indices must always be a multiple of three."); + for (uint32_t ct = 0; ct < encodedMesh.getNoOfIndices(); ct += 3) + { + decodedMesh.addTriangle(encodedMesh.getIndex(ct), encodedMesh.getIndex(ct + 1), encodedMesh.getIndex(ct + 2)); + } - result.m_offset = mesh.m_offset; + decodedMesh.setOffset(encodedMesh.getOffset()); - return result; + return decodedMesh; } } diff --git a/library/PolyVoxCore/include/PolyVoxCore/Mesh.inl b/library/PolyVoxCore/include/PolyVoxCore/Mesh.inl index e34f51ff..c574687f 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/Mesh.inl +++ b/library/PolyVoxCore/include/PolyVoxCore/Mesh.inl @@ -33,30 +33,54 @@ namespace PolyVox { } - template - const std::vector& Mesh::getIndices(void) const - { - return m_vecTriangleIndices; - } - - template - uint32_t Mesh::getNoOfIndices(void) const - { - return m_vecTriangleIndices.size(); - } - template IndexType Mesh::getNoOfVertices(void) const { return m_vecVertices.size(); } + template + const VertexType& Mesh::getVertex(IndexType index) const + { + return m_vecVertices[index]; + } + + template + const VertexType* Mesh::getRawVertexData(void) const + { + return &(m_vecVertices[0]); + } + template const std::vector& Mesh::getVertices(void) const { return m_vecVertices; } + template + uint32_t Mesh::getNoOfIndices(void) const + { + return m_vecIndices.size(); + } + + template + IndexType Mesh::getIndex(uint32_t index) const + { + return m_vecIndices[index]; + } + + template + const IndexType* Mesh::getRawIndexData(void) + { + return &(m_vecIndices[0]); + } + + template + const std::vector& Mesh::getIndices(void) const + { + return m_vecIndices; + } + template const Vector3DInt32& Mesh::getOffset(void) const { @@ -77,9 +101,9 @@ namespace PolyVox POLYVOX_ASSERT(index1 < m_vecVertices.size(), "Index points at an invalid vertex."); POLYVOX_ASSERT(index2 < m_vecVertices.size(), "Index points at an invalid vertex."); - m_vecTriangleIndices.push_back(index0); - m_vecTriangleIndices.push_back(index1); - m_vecTriangleIndices.push_back(index2); + m_vecIndices.push_back(index0); + m_vecIndices.push_back(index1); + m_vecIndices.push_back(index2); } template @@ -96,7 +120,7 @@ namespace PolyVox void Mesh::clear(void) { m_vecVertices.clear(); - m_vecTriangleIndices.clear(); + m_vecIndices.clear(); } template @@ -111,9 +135,9 @@ namespace PolyVox std::vector isVertexUsed(m_vecVertices.size()); std::fill(isVertexUsed.begin(), isVertexUsed.end(), false); - for(uint32_t triCt = 0; triCt < m_vecTriangleIndices.size(); triCt++) + for(uint32_t triCt = 0; triCt < m_vecIndices.size(); triCt++) { - int v = m_vecTriangleIndices[triCt]; + int v = m_vecIndices[triCt]; isVertexUsed[v] = true; } @@ -131,9 +155,9 @@ namespace PolyVox m_vecVertices.resize(noOfUsedVertices); - for(uint32_t triCt = 0; triCt < m_vecTriangleIndices.size(); triCt++) + for(uint32_t triCt = 0; triCt < m_vecIndices.size(); triCt++) { - m_vecTriangleIndices[triCt] = newPos[m_vecTriangleIndices[triCt]]; + m_vecIndices[triCt] = newPos[m_vecIndices[triCt]]; } } } diff --git a/library/PolyVoxCore/include/PolyVoxCore/PolyVoxForwardDeclarations.h b/library/PolyVoxCore/include/PolyVoxCore/PolyVoxForwardDeclarations.h index f72d5d37..0b401a98 100644 --- a/library/PolyVoxCore/include/PolyVoxCore/PolyVoxForwardDeclarations.h +++ b/library/PolyVoxCore/include/PolyVoxCore/PolyVoxForwardDeclarations.h @@ -111,7 +111,7 @@ namespace PolyVox //////////////////////////////////////////////////////////////////////////////// // MarchingCubesSurfaceExtractor //////////////////////////////////////////////////////////////////////////////// - template class MarchingCubesSurfaceExtractor; + template class MarchingCubesSurfaceExtractor; //////////////////////////////////////////////////////////////////////////////// // MarchingCubesVertex @@ -142,7 +142,8 @@ namespace PolyVox //////////////////////////////////////////////////////////////////////////////// // Mesh //////////////////////////////////////////////////////////////////////////////// - template class Mesh; + typedef uint32_t DefaultIndexType; + template class Mesh; //////////////////////////////////////////////////////////////////////////////// // Pager diff --git a/tests/TestCubicSurfaceExtractor.cpp b/tests/TestCubicSurfaceExtractor.cpp index 8476b7f8..906a2e80 100644 --- a/tests/TestCubicSurfaceExtractor.cpp +++ b/tests/TestCubicSurfaceExtractor.cpp @@ -26,6 +26,7 @@ freely, subject to the following restrictions: #include "PolyVoxCore/Density.h" #include "PolyVoxCore/Material.h" #include "PolyVoxCore/MaterialDensityPair.h" +#include "PolyVoxCore/RawVolume.h" #include "PolyVoxCore/SimpleVolume.h" #include "PolyVoxCore/CubicSurfaceExtractor.h" @@ -33,157 +34,148 @@ freely, subject to the following restrictions: using namespace PolyVox; +template +class CustomIsQuadNeeded +{ +public: + typedef _VoxelType VoxelType; -// These 'writeDensityValueToVoxel' functions provide a unified interface for writting densities to primative and class voxel types. -// They are conceptually the inverse of the 'convertToDensity' function used by the MarchingCubesSurfaceExtractor. They probably shouldn't be part -// of PolyVox, but they might be useful to other tests so we cold move them into a 'Tests.h' or something in the future. -template -void writeDensityValueToVoxel(int valueToWrite, VoxelType& voxel) -{ - voxel = valueToWrite; -} - -template<> -void writeDensityValueToVoxel(int valueToWrite, Density8& voxel) -{ - voxel.setDensity(valueToWrite); -} - -template<> -void writeDensityValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel) -{ - voxel.setDensity(valueToWrite); -} - -template -void writeMaterialValueToVoxel(int valueToWrite, VoxelType& voxel) -{ - //Most types don't have a material - return; -} - -template<> -void writeMaterialValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel) -{ - voxel.setMaterial(valueToWrite); -} + bool operator()(VoxelType back, VoxelType front, VoxelType& materialToUse) + { + // Not a useful test - it just does something different + // to the DefaultIsQuadNeeded so we can check it compiles. + if ((back > 1) && (front <= 1)) + { + materialToUse = static_cast(back); + return true; + } + else + { + return false; + } + } +}; // Runs the surface extractor for a given type. -template -uint32_t testForType(void) +template +VolumeType* createAndFillVolumeWithNoise(int32_t iVolumeSideLength, typename VolumeType::VoxelType minValue, typename VolumeType::VoxelType maxValue) { - const int32_t uVolumeSideLength = 256; - //Create empty volume - SimpleVolume volData(Region(Vector3DInt32(0,0,0), Vector3DInt32(uVolumeSideLength-1, uVolumeSideLength-1, uVolumeSideLength-1)), 128); + VolumeType* volData = new VolumeType(Region(Vector3DInt32(0, 0, 0), Vector3DInt32(iVolumeSideLength - 1, iVolumeSideLength - 1, iVolumeSideLength - 1))); + + // Seed generator for consistency between runs. + srand(12345); //Fill the volume with data - for (int32_t z = 0; z < uVolumeSideLength; z++) + for (int32_t z = 0; z < iVolumeSideLength; z++) { - for (int32_t y = 0; y < uVolumeSideLength; y++) + for (int32_t y = 0; y < iVolumeSideLength; y++) { - for (int32_t x = 0; x < uVolumeSideLength; x++) + for (int32_t x = 0; x < iVolumeSideLength; x++) { - if(x + y + z > uVolumeSideLength) + if (minValue == maxValue) { - VoxelType voxelValue; - writeDensityValueToVoxel(100, voxelValue); - writeMaterialValueToVoxel(42, voxelValue); - volData.setVoxelAt(x, y, z, voxelValue); + // In this case we are filling the whole volume with a single value. + volData->setVoxelAt(x, y, z, minValue); + } + else + { + // Otherwise we write random voxel values between zero and the requested maximum + int voxelValue = (rand() % (maxValue - minValue + 1)) + minValue; + volData->setVoxelAt(x, y, z, static_cast(voxelValue)); } } } } - uint32_t uTotalVertices = 0; - uint32_t uTotalIndices = 0; + return volData; +} - //Run the surface extractor a number of times over differnt regions of the volume. - const int32_t uRegionSideLength = 64; - for (int32_t z = 0; z < uVolumeSideLength; z += uRegionSideLength) +// Runs the surface extractor for a given type. +template +VolumeType* createAndFillVolumeRealistic(int32_t iVolumeSideLength) +{ + //Create empty volume + VolumeType* volData = new VolumeType(Region(Vector3DInt32(0, 0, 0), Vector3DInt32(iVolumeSideLength - 1, iVolumeSideLength - 1, iVolumeSideLength - 1))); + + // Seed generator for consistency between runs. + srand(12345); + + //Fill the volume with data + for (int32_t z = 0; z < iVolumeSideLength; z++) { - for (int32_t y = 0; y < uVolumeSideLength; y += uRegionSideLength) + for (int32_t y = 0; y < iVolumeSideLength; y++) { - for (int32_t x = 0; x < uVolumeSideLength; x += uRegionSideLength) + for (int32_t x = 0; x < iVolumeSideLength; x++) { - Region regionToExtract(x, y, z, x + uRegionSideLength - 1, y + uRegionSideLength - 1, z + uRegionSideLength - 1); - - auto result = extractCubicMesh(&volData, regionToExtract); - - uTotalVertices += result.getNoOfVertices(); - uTotalIndices += result.getNoOfIndices(); + // Should create a checker board pattern stretched along z? This is 'realistic' in the sense + // that it's not empty/random data, and should allow significant decimation to be performed. + if ((x ^ y) & 0x01) + { + volData->setVoxelAt(x, y, z, 0); + } + else + { + volData->setVoxelAt(x, y, z, 1); + } } } } - // Just some value which is representative of the work we've done. It doesn't - // matter what it is, just that it should be the same every time we run the test. - return uTotalVertices + uTotalIndices; + return volData; } -void TestCubicSurfaceExtractor::testExecute() +void TestCubicSurfaceExtractor::testBehaviour() { - /*const static uint32_t uExpectedVertices = 6624; - const static uint32_t uExpectedIndices = 9936; - const static uint32_t uMaterialToCheck = 3000; - const static float fExpectedMaterial = 42.0f; - const static uint32_t uIndexToCheck = 2000; - const static uint32_t uExpectedIndex = 1334; + // Test with default mesh and contoller types. + auto uint8Vol = createAndFillVolumeWithNoise< SimpleVolume >(32, 0, 2); + auto uint8Mesh = extractCubicMesh(uint8Vol, uint8Vol->getEnclosingRegion()); + QCOMPARE(uint8Mesh.getNoOfVertices(), uint32_t(57687)); + QCOMPARE(uint8Mesh.getNoOfIndices(), uint32_t(216234)); - Mesh mesh;*/ + // Test with default mesh type but user-provided controller. + auto int8Vol = createAndFillVolumeWithNoise< SimpleVolume >(32, 0, 2); + auto int8Mesh = extractCubicMesh(int8Vol, int8Vol->getEnclosingRegion(), CustomIsQuadNeeded()); + QCOMPARE(int8Mesh.getNoOfVertices(), uint32_t(29027)); + QCOMPARE(int8Mesh.getNoOfIndices(), uint32_t(178356)); - /*testForType(mesh); - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); + // Test with default controller but user-provided mesh. + auto uint32Vol = createAndFillVolumeWithNoise< SimpleVolume >(32, 0, 2); + Mesh< CubicVertex< uint32_t >, uint16_t > uint32Mesh; + extractCubicMeshCustom(uint32Vol, uint32Vol->getEnclosingRegion(), &uint32Mesh); + QCOMPARE(uint32Mesh.getNoOfVertices(), uint16_t(57687)); + QCOMPARE(uint32Mesh.getNoOfIndices(), uint32_t(216234)); - testForType(mesh); - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); + // Test with both mesh and controller being provided by the user. + auto int32Vol = createAndFillVolumeWithNoise< SimpleVolume >(32, 0, 2); + Mesh< CubicVertex< int32_t >, uint16_t > int32Mesh; + extractCubicMeshCustom(int32Vol, int32Vol->getEnclosingRegion(), &int32Mesh, CustomIsQuadNeeded()); + QCOMPARE(int32Mesh.getNoOfVertices(), uint16_t(29027)); + QCOMPARE(int32Mesh.getNoOfIndices(), uint32_t(178356)); +} - testForType(mesh); - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); +void TestCubicSurfaceExtractor::testEmptyVolumePerformance() +{ + auto emptyVol = createAndFillVolumeWithNoise< SimpleVolume >(128, 0, 0); + Mesh< CubicVertex< uint32_t >, uint16_t > emptyMesh; + QBENCHMARK{ extractCubicMeshCustom(emptyVol, Region(32, 32, 32, 63, 63, 63), &emptyMesh); } + QCOMPARE(emptyMesh.getNoOfVertices(), uint16_t(0)); +} - testForType(mesh); - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); +void TestCubicSurfaceExtractor::testRealisticVolumePerformance() +{ + auto realisticVol = createAndFillVolumeRealistic< SimpleVolume >(128); + Mesh< CubicVertex< uint32_t >, uint16_t > realisticMesh; + QBENCHMARK{ extractCubicMeshCustom(realisticVol, Region(32, 32, 32, 63, 63, 63), &realisticMesh); } + QCOMPARE(realisticMesh.getNoOfVertices(), uint16_t(2176)); +} - testForType(mesh); - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); - - testForType(mesh); - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); - - testForType(mesh); - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); - - testForType(mesh); - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);*/ - - /*testForType(mesh); - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);*/ - - const static uint32_t uExpectedSumOfVerticesAndIndices = 704668; - //const static uint32_t uExpectedSumOfVerticesAndIndices = 2792332; - uint32_t result = 0; - QBENCHMARK { - result = testForType(); - } - QCOMPARE(result, uExpectedSumOfVerticesAndIndices); +void TestCubicSurfaceExtractor::testNoiseVolumePerformance() +{ + auto noiseVol = createAndFillVolumeWithNoise< SimpleVolume >(128, 0, 2); + Mesh< CubicVertex< uint32_t >, uint16_t > noiseMesh; + QBENCHMARK{ extractCubicMeshCustom(noiseVol, Region(32, 32, 32, 63, 63, 63), &noiseMesh); } + QCOMPARE(noiseMesh.getNoOfVertices(), uint16_t(57729)); } QTEST_MAIN(TestCubicSurfaceExtractor) diff --git a/tests/TestCubicSurfaceExtractor.h b/tests/TestCubicSurfaceExtractor.h index 1ea815d8..31fb1cc4 100644 --- a/tests/TestCubicSurfaceExtractor.h +++ b/tests/TestCubicSurfaceExtractor.h @@ -31,7 +31,10 @@ class TestCubicSurfaceExtractor: public QObject Q_OBJECT private slots: - void testExecute(); + void testBehaviour(); + void testEmptyVolumePerformance(); + void testRealisticVolumePerformance(); + void testNoiseVolumePerformance(); }; #endif diff --git a/tests/TestSurfaceExtractor.cpp b/tests/TestSurfaceExtractor.cpp index d7be8937..95ebb299 100644 --- a/tests/TestSurfaceExtractor.cpp +++ b/tests/TestSurfaceExtractor.cpp @@ -52,12 +52,12 @@ public: float convertToMaterial(float /*voxel*/) { - return 1; + return 1.0f; } float blendMaterials(float /*a*/, float /*b*/, float /*weight*/) { - return 1; + return 1.0f; } float getThreshold(void) @@ -75,12 +75,6 @@ void writeDensityValueToVoxel(int valueToWrite, VoxelType& voxel) voxel = valueToWrite; } -template<> -void writeDensityValueToVoxel(int valueToWrite, Density8& voxel) -{ - voxel.setDensity(valueToWrite); -} - template<> void writeDensityValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel) { @@ -100,130 +94,137 @@ void writeMaterialValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel) voxel.setMaterial(valueToWrite); } -// Runs the surface extractor for a given type. template -Mesh > testForType(void) //I think we could avoid specifying this return type by using auto/decltype? +SimpleVolume* createAndFillVolume(void) { - const int32_t uVolumeSideLength = 32; + const int32_t uVolumeSideLength = 64; //Create empty volume - SimpleVolume volData(Region(Vector3DInt32(0,0,0), Vector3DInt32(uVolumeSideLength-1, uVolumeSideLength-1, uVolumeSideLength-1))); + SimpleVolume* volData = new SimpleVolume(Region(Vector3DInt32(0, 0, 0), Vector3DInt32(uVolumeSideLength - 1, uVolumeSideLength - 1, uVolumeSideLength - 1))); + // Fill for (int32_t z = 0; z < uVolumeSideLength; z++) { for (int32_t y = 0; y < uVolumeSideLength; y++) { for (int32_t x = 0; x < uVolumeSideLength; x++) { + // Create a density field which changes throughout the volume. It's + // zero in the lower corner and increasing as the coordinates increase. VoxelType voxelValue; - //Create a density field which changes throughout the volume. writeDensityValueToVoxel(x + y + z, voxelValue); - //Two different materials in two halves of the volume writeMaterialValueToVoxel(z > uVolumeSideLength / 2 ? 42 : 79, voxelValue); - volData.setVoxelAt(x, y, z, voxelValue); + volData->setVoxelAt(x, y, z, voxelValue); } } } - DefaultMarchingCubesController controller; - controller.setThreshold(50); - - auto result = extractMarchingCubesMesh(&volData, volData.getEnclosingRegion(), WrapModes::Border, VoxelType(), controller); - - return result; + return volData; } -void testCustomController(Mesh >& result) -{ - const int32_t uVolumeSideLength = 32; +// From http://stackoverflow.com/a/5289624 +float randomFloat(float a, float b) +{ + float random = ((float)rand()) / (float)RAND_MAX; + float diff = b - a; + float r = random * diff; + return a + r; +} +template +VolumeType* createAndFillVolumeWithNoise(int32_t iVolumeSideLength, float minValue, float maxValue) +{ //Create empty volume - SimpleVolume volData(Region(Vector3DInt32(0,0,0), Vector3DInt32(uVolumeSideLength-1, uVolumeSideLength-1, uVolumeSideLength-1))); + VolumeType* volData = new VolumeType(Region(Vector3DInt32(0, 0, 0), Vector3DInt32(iVolumeSideLength - 1, iVolumeSideLength - 1, iVolumeSideLength - 1))); - for (int32_t z = 0; z < uVolumeSideLength; z++) + // Seed generator for consistency between runs. + srand(12345); + + // Fill + for (int32_t z = 0; z < iVolumeSideLength; z++) { - for (int32_t y = 0; y < uVolumeSideLength; y++) + for (int32_t y = 0; y < iVolumeSideLength; y++) { - for (int32_t x = 0; x < uVolumeSideLength; x++) + for (int32_t x = 0; x < iVolumeSideLength; x++) { - float voxelValue = x + y + z; - volData.setVoxelAt(x, y, z, voxelValue); + float voxelValue = randomFloat(minValue, maxValue); + volData->setVoxelAt(x, y, z, voxelValue); } } } - CustomMarchingCubesController controller; - MarchingCubesSurfaceExtractor< SimpleVolume, CustomMarchingCubesController > extractor(&volData, volData.getEnclosingRegion(), &result, WrapModes::Border, 0, controller); - extractor.execute(); + return volData; } -void TestSurfaceExtractor::testExecute() +void TestSurfaceExtractor::testBehaviour() { - const static uint32_t uExpectedVertices = 4731; - const static uint32_t uExpectedIndices = 12810; - const static uint32_t uMaterialToCheck = 3000; - const static float fExpectedData = 1.0f; - const static float fNoMaterial = 1.0f; + // These tests apply the Marching Cubes surface extractor to volumes of various voxel types. In addition we sometimes make use of custom controllers + // and user-provided meshes to make sure these various combinations work as expected. + // + // It is also noted that the number of indices and vertices is varying quite significantly based on the voxel type. This seems unexpected, but could + // be explained if some overflow is occuring when writing data into the volume, causing volumes of different voxel types to have different distributions. + // Of course, the use of a custom controller will also make a significant diference, but this probably does need investigating further in the future. - Mesh > mesh; - //Run the test for various voxel types. - QBENCHMARK { - mesh = testForType(); - } - QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh.getVertices()[uMaterialToCheck].data, static_cast(fExpectedData)); + // This basic test just uses the default controller and automatically generates a mesh of the appropriate type. + auto uintVol = createAndFillVolume(); + auto uintMesh = extractMarchingCubesMesh(uintVol, uintVol->getEnclosingRegion()); + QCOMPARE(uintMesh.getNoOfVertices(), uint32_t(12096)); // Verifies size of mesh and that we have 32-bit indices + QCOMPARE(uintMesh.getNoOfIndices(), uint32_t(35157)); // Verifies size of mesh + QCOMPARE(uintMesh.getIndex(100), uint32_t(44)); // Verifies that we have 32-bit indices + QCOMPARE(uintMesh.getVertex(100).data, uint8_t(1)); // Not really meaningful for a primative type - auto mesh1 = testForType(); - QCOMPARE(mesh1.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh1.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh1.getVertices()[uMaterialToCheck].data, static_cast(fExpectedData)); + // This test makes use of a custom controller + auto floatVol = createAndFillVolume(); + CustomMarchingCubesController floatCustomController; + auto floatMesh = extractMarchingCubesMesh(floatVol, floatVol->getEnclosingRegion(), floatCustomController); + QCOMPARE(floatMesh.getNoOfVertices(), uint32_t(16113)); // Verifies size of mesh and that we have 32-bit indices + QCOMPARE(floatMesh.getNoOfIndices(), uint32_t(22053)); // Verifies size of mesh + QCOMPARE(floatMesh.getIndex(100), uint32_t(26)); // Verifies that we have 32-bit indices + QCOMPARE(floatMesh.getVertex(100).data, float(1.0f)); // Not really meaningful for a primative type - auto mesh2 = testForType(); - QCOMPARE(mesh2.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh2.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh2.getVertices()[uMaterialToCheck].data, static_cast(fExpectedData)); + // This test makes use of a user provided mesh. It uses the default controller, but we have to explicitly provide this because C++ won't let us + // use a default for the second-to-last parameter but noot use a default for the last parameter. + auto intVol = createAndFillVolume(); + Mesh< MarchingCubesVertex< int8_t >, uint16_t > intMesh; + extractMarchingCubesMeshCustom(intVol, intVol->getEnclosingRegion(), &intMesh); + QCOMPARE(intMesh.getNoOfVertices(), uint16_t(11718)); // Verifies size of mesh and that we have 16-bit indices + QCOMPARE(intMesh.getNoOfIndices(), uint32_t(34041)); // Verifies size of mesh + QCOMPARE(intMesh.getIndex(100), uint16_t(29)); // Verifies that we have 16-bit indices + QCOMPARE(intMesh.getVertex(100).data, int8_t(1)); // Not really meaningful for a primative type - auto mesh3 = testForType(); - QCOMPARE(mesh3.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh3.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh3.getVertices()[uMaterialToCheck].data, static_cast(fExpectedData)); + // This test makes use of a user-provided mesh and also a custom controller. + auto doubleVol = createAndFillVolume(); + CustomMarchingCubesController doubleCustomController; + Mesh< MarchingCubesVertex< double >, uint16_t > doubleMesh; + extractMarchingCubesMeshCustom(doubleVol, doubleVol->getEnclosingRegion(), &doubleMesh, doubleCustomController); + QCOMPARE(doubleMesh.getNoOfVertices(), uint16_t(16113)); // Verifies size of mesh and that we have 32-bit indices + QCOMPARE(doubleMesh.getNoOfIndices(), uint32_t(22053)); // Verifies size of mesh + QCOMPARE(doubleMesh.getIndex(100), uint16_t(26)); // Verifies that we have 32-bit indices + QCOMPARE(doubleMesh.getVertex(100).data, double(1.0f)); // Not really meaningful for a primative type - auto mesh4 = testForType(); - QCOMPARE(mesh4.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh4.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh4.getVertices()[uMaterialToCheck].data, static_cast(fExpectedData)); + // This test ensures the extractor works on a non-primitive voxel type. + auto materialVol = createAndFillVolume(); + auto materialMesh = extractMarchingCubesMesh(materialVol, materialVol->getEnclosingRegion()); + QCOMPARE(materialMesh.getNoOfVertices(), uint32_t(12096)); // Verifies size of mesh and that we have 32-bit indices + QCOMPARE(materialMesh.getNoOfIndices(), uint32_t(35157)); // Verifies size of mesh + QCOMPARE(materialMesh.getIndex(100), uint32_t(44)); // Verifies that we have 32-bit indices + QCOMPARE(materialMesh.getVertex(100).data.getMaterial(), uint16_t(79)); // Verify the data attached to the vertex +} - auto mesh5 = testForType(); - QCOMPARE(mesh5.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh5.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh5.getVertices()[uMaterialToCheck].data, static_cast(fExpectedData)); +void TestSurfaceExtractor::testEmptyVolumePerformance() +{ + auto emptyVol = createAndFillVolumeWithNoise< SimpleVolume >(128, -2.0f, -1.0f); + Mesh< MarchingCubesVertex< float >, uint16_t > emptyMesh; + QBENCHMARK{ extractMarchingCubesMeshCustom(emptyVol, Region(32, 32, 32, 63, 63, 63), &emptyMesh); } + QCOMPARE(emptyMesh.getNoOfVertices(), uint16_t(0)); +} - auto mesh6 = testForType(); - QCOMPARE(mesh6.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh6.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh6.getVertices()[uMaterialToCheck].data, static_cast(fExpectedData)); - - auto mesh7 = testForType(); - QCOMPARE(mesh7.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh7.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh7.getVertices()[uMaterialToCheck].data, static_cast(fExpectedData)); - - auto mesh8 = testForType(); - QCOMPARE(mesh8.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh8.getNoOfIndices(), uExpectedIndices); - QCOMPARE(mesh8.getVertices()[uMaterialToCheck].data, static_cast(fExpectedData)); - - auto mesh9 = testForType(); - QCOMPARE(mesh9.getNoOfVertices(), uExpectedVertices); - QCOMPARE(mesh9.getNoOfIndices(), uExpectedIndices); - //QCOMPARE(mesh9.getVertices()[uMaterialToCheck].data, fExpectedMaterial); - - //Test whether the CustomSurfaceExtractor works. - /*testCustomController(floatMesh); - QCOMPARE(floatMesh.getNoOfVertices(), uExpectedVertices); - QCOMPARE(floatMesh.getNoOfIndices(), uExpectedIndices); - QCOMPARE(floatMesh.getVertices()[uMaterialToCheck].data, fExpectedData);*/ +void TestSurfaceExtractor::testNoiseVolumePerformance() +{ + auto noiseVol = createAndFillVolumeWithNoise< SimpleVolume >(128, -1.0f, 1.0f); + Mesh< MarchingCubesVertex< float >, uint16_t > noiseMesh; + QBENCHMARK{ extractMarchingCubesMeshCustom(noiseVol, Region(32, 32, 32, 63, 63, 63), &noiseMesh); } + QCOMPARE(noiseMesh.getNoOfVertices(), uint16_t(48967)); } QTEST_MAIN(TestSurfaceExtractor) diff --git a/tests/TestSurfaceExtractor.h b/tests/TestSurfaceExtractor.h index 367d798e..20e5ee45 100644 --- a/tests/TestSurfaceExtractor.h +++ b/tests/TestSurfaceExtractor.h @@ -31,7 +31,9 @@ class TestSurfaceExtractor: public QObject Q_OBJECT private slots: - void testExecute(); + void testBehaviour(); + void testEmptyVolumePerformance(); + void testNoiseVolumePerformance(); }; #endif