Merge branch 'feature/mesh-work' into develop

This commit is contained in:
David Williams 2014-08-19 21:44:09 +02:00
commit 43e0d6f417
15 changed files with 462 additions and 405 deletions

View File

@ -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);
}

View File

@ -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 <typename DataType>
void addMesh(const PolyVox::Mesh< PolyVox::Vertex< DataType > >& surfaceMesh, const PolyVox::Vector3DInt32& translation = PolyVox::Vector3DInt32(0, 0, 0), float scale = 1.0f)
template <typename MeshType>
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);
}

View File

@ -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 <typename VertexDataType, typename IndexType = DefaultIndexType>
//using CubicMesh = Mesh< CubicVertex<VertexDataType>, IndexType >;
/// Decodes a position from a CubicVertex
inline Vector3DFloat decodePosition(const Vector3DUint8& encodedPosition)
{
@ -72,6 +77,102 @@ namespace PolyVox
return result;
}
/// Do not use this class directly. Use the 'extractCubicSurface' function instead (see examples).
template<typename VolumeType, typename MeshType, typename IsQuadNeeded>
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<Quad>& 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<Quad> > 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<typename VolumeType, typename MeshType, typename IsQuadNeeded = DefaultIsQuadNeeded<typename VolumeType::VoxelType> >
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<VolumeType, MeshType, IsQuadNeeded> 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
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Introduction
@ -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<typename VolumeType, typename MeshType, typename IsQuadNeeded>
class CubicSurfaceExtractor
template<typename VolumeType, typename IsQuadNeeded = DefaultIsQuadNeeded<typename VolumeType::VoxelType> >
Mesh<CubicVertex<typename VolumeType::VoxelType> > 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<Quad>& 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<Quad> > 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<typename VolumeType, typename IsQuadNeeded>
Mesh<CubicVertex<typename VolumeType::VoxelType> > extractCubicMesh(VolumeType* volData, Region region, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, bool bMergeQuads, IsQuadNeeded isQuadNeeded)
{
typedef Mesh<CubicVertex<typename VolumeType::VoxelType> > MeshType;
MeshType result;
CubicSurfaceExtractor<VolumeType, MeshType, IsQuadNeeded> extractor(volData, region, &result, eWrapMode, tBorderValue, bMergeQuads, isQuadNeeded);
extractor.execute();
Mesh< CubicVertex<typename VolumeType::VoxelType> > result;
extractCubicMeshCustom(volData, region, &result, isQuadNeeded, eWrapMode, tBorderValue, bMergeQuads);
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<CubicVertex<typename VolumeType::VoxelType> > extractCubicMesh(VolumeType* volData, Region region, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = VolumeType::VoxelType(), bool bMergeQuads = true)
#else
Mesh<CubicVertex<typename VolumeType::VoxelType> > extractCubicMesh(VolumeType* volData, Region region, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType(), bool bMergeQuads = true)
#endif
{
DefaultIsQuadNeeded<typename VolumeType::VoxelType> isQuadNeeded;
return extractCubicMesh<VolumeType, DefaultIsQuadNeeded<typename VolumeType::VoxelType> >(volData, region, eWrapMode, tBorderValue, bMergeQuads, isQuadNeeded);
}
}
#include "PolyVoxCore/CubicSurfaceExtractor.inl"

View File

@ -36,7 +36,7 @@ namespace PolyVox
const uint32_t CubicSurfaceExtractor<VolumeType, MeshType, IsQuadNeeded>::MaxVerticesPerPosition = 8;
template<typename VolumeType, typename MeshType, typename IsQuadNeeded>
CubicSurfaceExtractor<VolumeType, MeshType, IsQuadNeeded>::CubicSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, bool bMergeQuads, IsQuadNeeded isQuadNeeded)
CubicSurfaceExtractor<VolumeType, MeshType, IsQuadNeeded>::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)

View File

@ -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<DensityType>::min)() + (std::numeric_limits<DensityType>::max)()) / 2)
{
if (std::is_signed<DensityType>())
{
m_tThreshold = DensityType(0);
}
else
{
m_tThreshold = (((std::numeric_limits<DensityType>::min)() + (std::numeric_limits<DensityType>::max)()) / 2);
}
}
/**

View File

@ -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__

View File

@ -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 <typename VertexDataType, typename IndexType = DefaultIndexType>
//using MarchingCubesMesh = Mesh< MarchingCubesVertex<VertexDataType>, 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<uint16_t>(px + 0.5f);
@ -147,17 +152,12 @@ namespace PolyVox
return result;
}
template< typename VolumeType, typename Controller = DefaultMarchingCubesController<typename VolumeType::VoxelType> >
/// 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<MarchingCubesVertex<typename VolumeType::VoxelType> >* result, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = VolumeType::VoxelType(), Controller controller = Controller());
#else
MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> >* 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<MarchingCubesVertex<typename VolumeType::VoxelType> >* 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<MarchingCubesVertex<typename VolumeType::VoxelType> > 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<typename VolumeType::VoxelType> >
void extractMarchingCubesMeshCustom(VolumeType* volData, Region region, MeshType* result, ControllerType controller = ControllerType(), WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType())
{
Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> > result;
MarchingCubesSurfaceExtractor<VolumeType, Controller> extractor(volData, region, &result, eWrapMode, tBorderValue, controller);
MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType> 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<MarchingCubesVertex<typename VolumeType::VoxelType> > extractMarchingCubesMesh(VolumeType* volData, Region region, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = VolumeType::VoxelType())
#else
Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> > extractMarchingCubesMesh(VolumeType* volData, Region region, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType())
#endif
template< typename VolumeType, typename ControllerType = DefaultMarchingCubesController<typename VolumeType::VoxelType> >
Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> > extractMarchingCubesMesh(VolumeType* volData, Region region, ControllerType controller = ControllerType(), WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType())
{
DefaultMarchingCubesController<typename VolumeType::VoxelType> controller;
return extractMarchingCubesMesh(volData, region, eWrapMode, tBorderValue, controller);
Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> > result;
extractMarchingCubesMeshCustom<VolumeType, Mesh<MarchingCubesVertex<typename VolumeType::VoxelType>, DefaultIndexType > >(volData, region, &result, controller, eWrapMode, tBorderValue);
return result;
}
}

View File

@ -25,8 +25,8 @@ freely, subject to the following restrictions:
namespace PolyVox
{
template<typename VolumeType, typename Controller>
MarchingCubesSurfaceExtractor<VolumeType, Controller>::MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> >* result, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, Controller controller)
template<typename VolumeType, typename MeshType, typename ControllerType>
MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::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<typename VolumeType, typename Controller>
void MarchingCubesSurfaceExtractor<VolumeType, Controller>::execute()
template<typename VolumeType, typename MeshType, typename ControllerType>
void MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::execute()
{
Timer timer;
m_meshCurrent->clear();
@ -129,9 +130,9 @@ namespace PolyVox
<< "x" << m_regSizeInVoxels.getDepthInVoxels() << ")");
}
template<typename VolumeType, typename Controller>
template<typename VolumeType, typename MeshType, typename ControllerType>
template<bool isPrevZAvail>
uint32_t MarchingCubesSurfaceExtractor<VolumeType, Controller>::computeBitmaskForSlice(const Array2DUint8& pPreviousBitmask, Array2DUint8& pCurrentBitmask)
uint32_t MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::computeBitmaskForSlice(const Array2DUint8& pPreviousBitmask, Array2DUint8& pCurrentBitmask)
{
m_uNoOfOccupiedCells = 0;
@ -195,9 +196,9 @@ namespace PolyVox
return m_uNoOfOccupiedCells;
}
template<typename VolumeType, typename Controller>
template<typename VolumeType, typename MeshType, typename ControllerType>
template<bool isPrevXAvail, bool isPrevYAvail, bool isPrevZAvail>
void MarchingCubesSurfaceExtractor<VolumeType, Controller>::computeBitmaskForCell(const Array2DUint8& pPreviousBitmask, Array2DUint8& pCurrentBitmask, uint32_t uXRegSpace, uint32_t uYRegSpace)
void MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::computeBitmaskForCell(const Array2DUint8& pPreviousBitmask, Array2DUint8& pCurrentBitmask, uint32_t uXRegSpace, uint32_t uYRegSpace)
{
uint8_t iCubeIndex = 0;
@ -395,8 +396,8 @@ namespace PolyVox
}
}
template<typename VolumeType, typename Controller>
void MarchingCubesSurfaceExtractor<VolumeType, Controller>::generateVerticesForSlice(const Array2DUint8& pCurrentBitmask,
template<typename VolumeType, typename MeshType, typename ControllerType>
void MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::generateVerticesForSlice(const Array2DUint8& pCurrentBitmask,
Array2DInt32& m_pCurrentVertexIndicesX,
Array2DInt32& m_pCurrentVertexIndicesY,
Array2DInt32& m_pCurrentVertexIndicesZ)
@ -535,8 +536,8 @@ namespace PolyVox
}
}
template<typename VolumeType, typename Controller>
void MarchingCubesSurfaceExtractor<VolumeType, Controller>::generateIndicesForSlice(const Array2DUint8& pPreviousBitmask,
template<typename VolumeType, typename MeshType, typename ControllerType>
void MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::generateIndicesForSlice(const Array2DUint8& pPreviousBitmask,
const Array2DInt32& m_pPreviousVertexIndicesX,
const Array2DInt32& m_pPreviousVertexIndicesY,
const Array2DInt32& m_pPreviousVertexIndicesZ,

View File

@ -50,43 +50,51 @@ namespace PolyVox
Mesh();
~Mesh();
const std::vector<IndexType>& getIndices(void) const;
uint32_t getNoOfIndices(void) const;
IndexType getNoOfVertices(void) const;
const std::vector<VertexType>& getVertices(void) const;
const Vector3DInt32& getOffset(void) const;
const VertexType& getVertex(IndexType index) const;
const VertexType* getRawVertexData(void) const;
POLYVOX_DEPRECATED const std::vector<VertexType>& getVertices(void) const;
uint32_t getNoOfIndices(void) const;
IndexType getIndex(uint32_t index) const;
const IndexType* getRawIndexData(void);
POLYVOX_DEPRECATED const std::vector<IndexType>& 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;
public:
std::vector<IndexType> m_vecTriangleIndices;
private:
std::vector<IndexType> m_vecIndices;
std::vector<VertexType> m_vecVertices;
Vector3DInt32 m_offset;
};
template <typename MeshType>
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;
}
}

View File

@ -33,30 +33,54 @@ namespace PolyVox
{
}
template <typename VertexType, typename IndexType>
const std::vector<IndexType>& Mesh<VertexType, IndexType>::getIndices(void) const
{
return m_vecTriangleIndices;
}
template <typename VertexType, typename IndexType>
uint32_t Mesh<VertexType, IndexType>::getNoOfIndices(void) const
{
return m_vecTriangleIndices.size();
}
template <typename VertexType, typename IndexType>
IndexType Mesh<VertexType, IndexType>::getNoOfVertices(void) const
{
return m_vecVertices.size();
}
template <typename VertexType, typename IndexType>
const VertexType& Mesh<VertexType, IndexType>::getVertex(IndexType index) const
{
return m_vecVertices[index];
}
template <typename VertexType, typename IndexType>
const VertexType* Mesh<VertexType, IndexType>::getRawVertexData(void) const
{
return &(m_vecVertices[0]);
}
template <typename VertexType, typename IndexType>
const std::vector<VertexType>& Mesh<VertexType, IndexType>::getVertices(void) const
{
return m_vecVertices;
}
template <typename VertexType, typename IndexType>
uint32_t Mesh<VertexType, IndexType>::getNoOfIndices(void) const
{
return m_vecIndices.size();
}
template <typename VertexType, typename IndexType>
IndexType Mesh<VertexType, IndexType>::getIndex(uint32_t index) const
{
return m_vecIndices[index];
}
template <typename VertexType, typename IndexType>
const IndexType* Mesh<VertexType, IndexType>::getRawIndexData(void)
{
return &(m_vecIndices[0]);
}
template <typename VertexType, typename IndexType>
const std::vector<IndexType>& Mesh<VertexType, IndexType>::getIndices(void) const
{
return m_vecIndices;
}
template <typename VertexType, typename IndexType>
const Vector3DInt32& Mesh<VertexType, IndexType>::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 <typename VertexType, typename IndexType>
@ -96,7 +120,7 @@ namespace PolyVox
void Mesh<VertexType, IndexType>::clear(void)
{
m_vecVertices.clear();
m_vecTriangleIndices.clear();
m_vecIndices.clear();
}
template <typename VertexType, typename IndexType>
@ -111,9 +135,9 @@ namespace PolyVox
std::vector<bool> 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]];
}
}
}

View File

@ -111,7 +111,7 @@ namespace PolyVox
////////////////////////////////////////////////////////////////////////////////
// MarchingCubesSurfaceExtractor
////////////////////////////////////////////////////////////////////////////////
template<typename VolumeType, typename Controller> class MarchingCubesSurfaceExtractor;
template<typename VolumeType, typename MeshType, typename ControllerType> class MarchingCubesSurfaceExtractor;
////////////////////////////////////////////////////////////////////////////////
// MarchingCubesVertex
@ -142,7 +142,8 @@ namespace PolyVox
////////////////////////////////////////////////////////////////////////////////
// Mesh
////////////////////////////////////////////////////////////////////////////////
template <typename VertexType, typename IndexType = uint32_t> class Mesh;
typedef uint32_t DefaultIndexType;
template <typename VertexType, typename IndexType = DefaultIndexType> class Mesh;
////////////////////////////////////////////////////////////////////////////////
// Pager

View File

@ -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;
// 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<typename VoxelType>
void writeDensityValueToVoxel(int valueToWrite, VoxelType& voxel)
template<typename _VoxelType>
class CustomIsQuadNeeded
{
voxel = valueToWrite;
public:
typedef _VoxelType VoxelType;
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<VoxelType>(back);
return true;
}
else
{
return false;
}
}
};
// Runs the surface extractor for a given type.
template <typename VolumeType>
VolumeType* createAndFillVolumeWithNoise(int32_t iVolumeSideLength, typename VolumeType::VoxelType minValue, typename VolumeType::VoxelType maxValue)
{
//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 < iVolumeSideLength; y++)
{
for (int32_t x = 0; x < iVolumeSideLength; x++)
{
if (minValue == maxValue)
{
// 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<typename VolumeType::VoxelType>(voxelValue));
}
}
}
}
template<>
void writeDensityValueToVoxel(int valueToWrite, Density8& voxel)
{
voxel.setDensity(valueToWrite);
}
template<>
void writeDensityValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel)
{
voxel.setDensity(valueToWrite);
}
template<typename VoxelType>
void writeMaterialValueToVoxel(int valueToWrite, VoxelType& voxel)
{
//Most types don't have a material
return;
}
template<>
void writeMaterialValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel)
{
voxel.setMaterial(valueToWrite);
return volData;
}
// Runs the surface extractor for a given type.
template <typename VoxelType>
uint32_t testForType(void)
template <typename VolumeType>
VolumeType* createAndFillVolumeRealistic(int32_t iVolumeSideLength)
{
const int32_t uVolumeSideLength = 256;
//Create empty volume
SimpleVolume<VoxelType> 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)
// 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)
{
VoxelType voxelValue;
writeDensityValueToVoxel<VoxelType>(100, voxelValue);
writeMaterialValueToVoxel<VoxelType>(42, voxelValue);
volData.setVoxelAt(x, y, z, voxelValue);
volData->setVoxelAt(x, y, z, 0);
}
}
}
}
uint32_t uTotalVertices = 0;
uint32_t uTotalIndices = 0;
//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)
else
{
for (int32_t y = 0; y < uVolumeSideLength; y += uRegionSideLength)
volData->setVoxelAt(x, y, z, 1);
}
}
}
}
return volData;
}
void TestCubicSurfaceExtractor::testBehaviour()
{
for (int32_t x = 0; x < uVolumeSideLength; x += uRegionSideLength)
// Test with default mesh and contoller types.
auto uint8Vol = createAndFillVolumeWithNoise< SimpleVolume<uint8_t> >(32, 0, 2);
auto uint8Mesh = extractCubicMesh(uint8Vol, uint8Vol->getEnclosingRegion());
QCOMPARE(uint8Mesh.getNoOfVertices(), uint32_t(57687));
QCOMPARE(uint8Mesh.getNoOfIndices(), uint32_t(216234));
// Test with default mesh type but user-provided controller.
auto int8Vol = createAndFillVolumeWithNoise< SimpleVolume<int8_t> >(32, 0, 2);
auto int8Mesh = extractCubicMesh(int8Vol, int8Vol->getEnclosingRegion(), CustomIsQuadNeeded<int8_t>());
QCOMPARE(int8Mesh.getNoOfVertices(), uint32_t(29027));
QCOMPARE(int8Mesh.getNoOfIndices(), uint32_t(178356));
// Test with default controller but user-provided mesh.
auto uint32Vol = createAndFillVolumeWithNoise< SimpleVolume<uint32_t> >(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));
// Test with both mesh and controller being provided by the user.
auto int32Vol = createAndFillVolumeWithNoise< SimpleVolume<int32_t> >(32, 0, 2);
Mesh< CubicVertex< int32_t >, uint16_t > int32Mesh;
extractCubicMeshCustom(int32Vol, int32Vol->getEnclosingRegion(), &int32Mesh, CustomIsQuadNeeded<int32_t>());
QCOMPARE(int32Mesh.getNoOfVertices(), uint16_t(29027));
QCOMPARE(int32Mesh.getNoOfIndices(), uint32_t(178356));
}
void TestCubicSurfaceExtractor::testEmptyVolumePerformance()
{
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();
}
}
auto emptyVol = createAndFillVolumeWithNoise< SimpleVolume<uint32_t> >(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));
}
// 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;
}
void TestCubicSurfaceExtractor::testExecute()
void TestCubicSurfaceExtractor::testRealisticVolumePerformance()
{
/*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;
Mesh<CubicVertex> mesh;*/
/*testForType<int8_t>(mesh);
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);
testForType<uint8_t>(mesh);
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);
testForType<int16_t>(mesh);
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);
testForType<uint16_t>(mesh);
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);
testForType<int32_t>(mesh);
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);
testForType<uint32_t>(mesh);
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);
testForType<float>(mesh);
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);
testForType<double>(mesh);
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial);*/
/*testForType<Material8>(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<MaterialDensityPair88>();
auto realisticVol = createAndFillVolumeRealistic< SimpleVolume<uint32_t> >(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));
}
QCOMPARE(result, uExpectedSumOfVerticesAndIndices);
void TestCubicSurfaceExtractor::testNoiseVolumePerformance()
{
auto noiseVol = createAndFillVolumeWithNoise< SimpleVolume<uint32_t> >(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)

View File

@ -31,7 +31,10 @@ class TestCubicSurfaceExtractor: public QObject
Q_OBJECT
private slots:
void testExecute();
void testBehaviour();
void testEmptyVolumePerformance();
void testRealisticVolumePerformance();
void testNoiseVolumePerformance();
};
#endif

View File

@ -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 <typename VoxelType>
Mesh<MarchingCubesVertex<VoxelType> > testForType(void) //I think we could avoid specifying this return type by using auto/decltype?
SimpleVolume<VoxelType>* createAndFillVolume(void)
{
const int32_t uVolumeSideLength = 32;
const int32_t uVolumeSideLength = 64;
//Create empty volume
SimpleVolume<VoxelType> volData(Region(Vector3DInt32(0,0,0), Vector3DInt32(uVolumeSideLength-1, uVolumeSideLength-1, uVolumeSideLength-1)));
SimpleVolume<VoxelType>* volData = new SimpleVolume<VoxelType>(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<VoxelType>(x + y + z, voxelValue);
//Two different materials in two halves of the volume
writeMaterialValueToVoxel<VoxelType>(z > uVolumeSideLength / 2 ? 42 : 79, voxelValue);
volData.setVoxelAt(x, y, z, voxelValue);
volData->setVoxelAt(x, y, z, voxelValue);
}
}
}
DefaultMarchingCubesController<VoxelType> controller;
controller.setThreshold(50);
auto result = extractMarchingCubesMesh(&volData, volData.getEnclosingRegion(), WrapModes::Border, VoxelType(), controller);
return result;
return volData;
}
void testCustomController(Mesh<MarchingCubesVertex<float> >& result)
// From http://stackoverflow.com/a/5289624
float randomFloat(float a, float b)
{
const int32_t uVolumeSideLength = 32;
float random = ((float)rand()) / (float)RAND_MAX;
float diff = b - a;
float r = random * diff;
return a + r;
}
template <typename VolumeType>
VolumeType* createAndFillVolumeWithNoise(int32_t iVolumeSideLength, float minValue, float maxValue)
{
//Create empty volume
SimpleVolume<float> 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<float>, 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<MarchingCubesVertex<int8_t> > mesh;
//Run the test for various voxel types.
QBENCHMARK {
mesh = testForType<int8_t>();
// This basic test just uses the default controller and automatically generates a mesh of the appropriate type.
auto uintVol = createAndFillVolume<uint8_t>();
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
// This test makes use of a custom controller
auto floatVol = createAndFillVolume<float>();
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
// 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<int8_t>();
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
// This test makes use of a user-provided mesh and also a custom controller.
auto doubleVol = createAndFillVolume<double>();
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
// This test ensures the extractor works on a non-primitive voxel type.
auto materialVol = createAndFillVolume<MaterialDensityPair88>();
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
}
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].data, static_cast<int8_t>(fExpectedData));
auto mesh1 = testForType<uint8_t>();
QCOMPARE(mesh1.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh1.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh1.getVertices()[uMaterialToCheck].data, static_cast<uint8_t>(fExpectedData));
void TestSurfaceExtractor::testEmptyVolumePerformance()
{
auto emptyVol = createAndFillVolumeWithNoise< SimpleVolume<float> >(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 mesh2 = testForType<int16_t>();
QCOMPARE(mesh2.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh2.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh2.getVertices()[uMaterialToCheck].data, static_cast<int16_t>(fExpectedData));
auto mesh3 = testForType<uint16_t>();
QCOMPARE(mesh3.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh3.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh3.getVertices()[uMaterialToCheck].data, static_cast<uint16_t>(fExpectedData));
auto mesh4 = testForType<int32_t>();
QCOMPARE(mesh4.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh4.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh4.getVertices()[uMaterialToCheck].data, static_cast<int32_t>(fExpectedData));
auto mesh5 = testForType<uint32_t>();
QCOMPARE(mesh5.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh5.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh5.getVertices()[uMaterialToCheck].data, static_cast<uint32_t>(fExpectedData));
auto mesh6 = testForType<float>();
QCOMPARE(mesh6.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh6.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh6.getVertices()[uMaterialToCheck].data, static_cast<float>(fExpectedData));
auto mesh7 = testForType<double>();
QCOMPARE(mesh7.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh7.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh7.getVertices()[uMaterialToCheck].data, static_cast<double>(fExpectedData));
auto mesh8 = testForType<Density8>();
QCOMPARE(mesh8.getNoOfVertices(), uExpectedVertices);
QCOMPARE(mesh8.getNoOfIndices(), uExpectedIndices);
QCOMPARE(mesh8.getVertices()[uMaterialToCheck].data, static_cast<Density8>(fExpectedData));
auto mesh9 = testForType<MaterialDensityPair88>();
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<float> >(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)

View File

@ -31,7 +31,9 @@ class TestSurfaceExtractor: public QObject
Q_OBJECT
private slots:
void testExecute();
void testBehaviour();
void testEmptyVolumePerformance();
void testNoiseVolumePerformance();
};
#endif