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 // Bind the vertex array for the current mesh
glBindVertexArray(meshData.vertexArrayObject); glBindVertexArray(meshData.vertexArrayObject);
// Draw the mesh // Draw the mesh
glDrawElements(GL_TRIANGLES, meshData.noOfIndices, GL_UNSIGNED_INT, 0); glDrawElements(GL_TRIANGLES, meshData.noOfIndices, meshData.indexType, 0);
// Unbind the vertex array. // Unbind the vertex array.
glBindVertexArray(0); glBindVertexArray(0);
} }

View File

@ -36,6 +36,7 @@ distribution.
struct OpenGLMeshData struct OpenGLMeshData
{ {
GLuint noOfIndices; GLuint noOfIndices;
GLenum indexType;
GLuint indexBuffer; GLuint indexBuffer;
GLuint vertexBuffer; GLuint vertexBuffer;
GLuint vertexArrayObject; GLuint vertexArrayObject;
@ -53,8 +54,8 @@ public:
OpenGLWidget(QWidget *parent); OpenGLWidget(QWidget *parent);
// Convert a PolyVox mesh to OpenGL index/vertex buffers. Inlined because it's templatised. // Convert a PolyVox mesh to OpenGL index/vertex buffers. Inlined because it's templatised.
template <typename DataType> template <typename MeshType>
void addMesh(const PolyVox::Mesh< PolyVox::Vertex< DataType > >& surfaceMesh, const PolyVox::Vector3DInt32& translation = PolyVox::Vector3DInt32(0, 0, 0), float scale = 1.0f) 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 // Convienient access to the vertices and indices
const auto& vecIndices = surfaceMesh.getIndices(); const auto& vecIndices = surfaceMesh.getIndices();
@ -71,29 +72,29 @@ public:
// The GL_ARRAY_BUFFER will contain the list of vertex positions // The GL_ARRAY_BUFFER will contain the list of vertex positions
glGenBuffers(1, &(meshData.vertexBuffer)); glGenBuffers(1, &(meshData.vertexBuffer));
glBindBuffer(GL_ARRAY_BUFFER, 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 // and GL_ELEMENT_ARRAY_BUFFER will contain the indices
glGenBuffers(1, &(meshData.indexBuffer)); glGenBuffers(1, &(meshData.indexBuffer));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 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 // 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 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 // 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 // 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 // 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. // chosen surface extractor generates normals and can skip uploading them if not.
glEnableVertexAttribArray(1); // Attrib '1' is the vertex normals. 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 // 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. // 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' 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) 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(PolyVox::Vertex< DataType >), (GLvoid*)(offsetof(PolyVox::Vertex< DataType >, data))); glVertexAttribIPointer(2, size, GL_UNSIGNED_BYTE, sizeof(typename MeshType::VertexType), (GLvoid*)(offsetof(typename MeshType::VertexType, data)));
// We're done uploading and can now unbind. // We're done uploading and can now unbind.
glBindVertexArray(0); glBindVertexArray(0);
@ -103,6 +104,9 @@ public:
meshData.translation = QVector3D(translation.getX(), translation.getY(), translation.getZ()); meshData.translation = QVector3D(translation.getX(), translation.getY(), translation.getZ());
meshData.scale = scale; 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. // Now add the mesh to the list of meshes to render.
addMeshData(meshData); addMeshData(meshData);
} }

View File

@ -53,6 +53,11 @@ namespace PolyVox
DataType data; 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 /// Decodes a position from a CubicVertex
inline Vector3DFloat decodePosition(const Vector3DUint8& encodedPosition) inline Vector3DFloat decodePosition(const Vector3DUint8& encodedPosition)
{ {
@ -72,6 +77,102 @@ namespace PolyVox
return result; 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 /// The CubicSurfaceExtractor creates a mesh in which each voxel appears to be rendered as a cube
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// Introduction /// 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. /// 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> template<typename VolumeType, typename IsQuadNeeded = DefaultIsQuadNeeded<typename VolumeType::VoxelType> >
class CubicSurfaceExtractor 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 Mesh< CubicVertex<typename VolumeType::VoxelType> > result;
{ extractCubicMeshCustom(volData, region, &result, isQuadNeeded, eWrapMode, tBorderValue, bMergeQuads);
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();
return result; 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" #include "PolyVoxCore/CubicSurfaceExtractor.inl"

View File

@ -36,7 +36,7 @@ namespace PolyVox
const uint32_t CubicSurfaceExtractor<VolumeType, MeshType, IsQuadNeeded>::MaxVerticesPerPosition = 8; const uint32_t CubicSurfaceExtractor<VolumeType, MeshType, IsQuadNeeded>::MaxVerticesPerPosition = 8;
template<typename VolumeType, typename MeshType, typename IsQuadNeeded> 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_volData(volData)
,m_regSizeInVoxels(region) ,m_regSizeInVoxels(region)
,m_meshCurrent(result) ,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. * 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) 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__ #ifndef __PolyVox_TypeDef_H__
#define __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 //Definitions needed to make library functions accessable
// See http://gcc.gnu.org/wiki/Visibility for more info. // See http://gcc.gnu.org/wiki/Visibility for more info.
#if defined _WIN32 || defined __CYGWIN__ #if defined _WIN32 || defined __CYGWIN__

View File

@ -55,6 +55,11 @@ namespace PolyVox
DataType data; 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 /// Decodes a position from a MarchingCubesVertex
inline Vector3DFloat decodePosition(const Vector3DUint16& encodedPosition) inline Vector3DFloat decodePosition(const Vector3DUint16& encodedPosition)
{ {
@ -90,8 +95,8 @@ namespace PolyVox
// floats into two bytes and store them in a single uint16_t // floats into two bytes and store them in a single uint16_t
// Move from range [-1.0f, 1.0f] to [0.0f, 255.0f] // Move from range [-1.0f, 1.0f] to [0.0f, 255.0f]
px = (px + 1.0) * 127.5f; px = (px + 1.0f) * 127.5f;
py = (py + 1.0) * 127.5f; py = (py + 1.0f) * 127.5f;
// Convert to uints // Convert to uints
uint16_t resultX = static_cast<uint16_t>(px + 0.5f); uint16_t resultX = static_cast<uint16_t>(px + 0.5f);
@ -147,17 +152,12 @@ namespace PolyVox
return result; 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 class MarchingCubesSurfaceExtractor
{ {
public: 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 MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, ControllerType controller, WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType());
// 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
void execute(); void execute();
@ -306,7 +306,7 @@ namespace PolyVox
uint32_t m_uNoOfOccupiedCells; uint32_t m_uNoOfOccupiedCells;
//The surface patch we are currently filling. //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 //Information about the region we are currently processing
Region m_regSizeInVoxels; Region m_regSizeInVoxels;
@ -318,32 +318,38 @@ namespace PolyVox
Region m_regSliceCurrent; Region m_regSliceCurrent;
//Used to convert arbitrary voxel types in densities and materials. //Used to convert arbitrary voxel types in densities and materials.
Controller m_controller; ControllerType m_controller;
//Our threshold value //Our threshold value
typename Controller::DensityType m_tThreshold; typename ControllerType::DensityType m_tThreshold;
}; };
template< typename VolumeType, typename Controller> // This version of the function performs the extraction into a user-provided mesh rather than allocating a mesh automatically.
Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> > extractMarchingCubesMesh(VolumeType* volData, Region region, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, Controller controller) // 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, MeshType, ControllerType> extractor(volData, region, result, controller, eWrapMode, tBorderValue);
MarchingCubesSurfaceExtractor<VolumeType, Controller> extractor(volData, region, &result, eWrapMode, tBorderValue, controller);
extractor.execute(); extractor.execute();
return result;
} }
template< typename VolumeType> template< typename VolumeType, typename ControllerType = DefaultMarchingCubesController<typename VolumeType::VoxelType> >
// 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 Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> > extractMarchingCubesMesh(VolumeType* volData, Region region, ControllerType controller = ControllerType(), WrapMode eWrapMode = WrapModes::Border, typename VolumeType::VoxelType tBorderValue = typename VolumeType::VoxelType())
// 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
{ {
DefaultMarchingCubesController<typename VolumeType::VoxelType> controller; Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> > result;
return extractMarchingCubesMesh(volData, region, eWrapMode, tBorderValue, controller); 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 namespace PolyVox
{ {
template<typename VolumeType, typename Controller> template<typename VolumeType, typename MeshType, typename ControllerType>
MarchingCubesSurfaceExtractor<VolumeType, Controller>::MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, Mesh<MarchingCubesVertex<typename VolumeType::VoxelType> >* result, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue, Controller controller) MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::MarchingCubesSurfaceExtractor(VolumeType* volData, Region region, MeshType* result, ControllerType controller, WrapMode eWrapMode, typename VolumeType::VoxelType tBorderValue)
:m_volData(volData) :m_volData(volData)
,m_sampVolume(volData) ,m_sampVolume(volData)
,m_meshCurrent(result) ,m_meshCurrent(result)
@ -34,6 +34,7 @@ namespace PolyVox
,m_controller(controller) ,m_controller(controller)
,m_tThreshold(m_controller.getThreshold()) ,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_regSizeInVoxels.cropTo(m_volData->getEnclosingRegion());
m_regSizeInCells = m_regSizeInVoxels; m_regSizeInCells = m_regSizeInVoxels;
m_regSizeInCells.setUpperCorner(m_regSizeInCells.getUpperCorner() - Vector3DInt32(1,1,1)); m_regSizeInCells.setUpperCorner(m_regSizeInCells.getUpperCorner() - Vector3DInt32(1,1,1));
@ -41,8 +42,8 @@ namespace PolyVox
m_sampVolume.setWrapMode(eWrapMode, tBorderValue); m_sampVolume.setWrapMode(eWrapMode, tBorderValue);
} }
template<typename VolumeType, typename Controller> template<typename VolumeType, typename MeshType, typename ControllerType>
void MarchingCubesSurfaceExtractor<VolumeType, Controller>::execute() void MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::execute()
{ {
Timer timer; Timer timer;
m_meshCurrent->clear(); m_meshCurrent->clear();
@ -129,9 +130,9 @@ namespace PolyVox
<< "x" << m_regSizeInVoxels.getDepthInVoxels() << ")"); << "x" << m_regSizeInVoxels.getDepthInVoxels() << ")");
} }
template<typename VolumeType, typename Controller> template<typename VolumeType, typename MeshType, typename ControllerType>
template<bool isPrevZAvail> 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; m_uNoOfOccupiedCells = 0;
@ -195,9 +196,9 @@ namespace PolyVox
return m_uNoOfOccupiedCells; return m_uNoOfOccupiedCells;
} }
template<typename VolumeType, typename Controller> template<typename VolumeType, typename MeshType, typename ControllerType>
template<bool isPrevXAvail, bool isPrevYAvail, bool isPrevZAvail> 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; uint8_t iCubeIndex = 0;
@ -395,8 +396,8 @@ namespace PolyVox
} }
} }
template<typename VolumeType, typename Controller> template<typename VolumeType, typename MeshType, typename ControllerType>
void MarchingCubesSurfaceExtractor<VolumeType, Controller>::generateVerticesForSlice(const Array2DUint8& pCurrentBitmask, void MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::generateVerticesForSlice(const Array2DUint8& pCurrentBitmask,
Array2DInt32& m_pCurrentVertexIndicesX, Array2DInt32& m_pCurrentVertexIndicesX,
Array2DInt32& m_pCurrentVertexIndicesY, Array2DInt32& m_pCurrentVertexIndicesY,
Array2DInt32& m_pCurrentVertexIndicesZ) Array2DInt32& m_pCurrentVertexIndicesZ)
@ -535,8 +536,8 @@ namespace PolyVox
} }
} }
template<typename VolumeType, typename Controller> template<typename VolumeType, typename MeshType, typename ControllerType>
void MarchingCubesSurfaceExtractor<VolumeType, Controller>::generateIndicesForSlice(const Array2DUint8& pPreviousBitmask, void MarchingCubesSurfaceExtractor<VolumeType, MeshType, ControllerType>::generateIndicesForSlice(const Array2DUint8& pPreviousBitmask,
const Array2DInt32& m_pPreviousVertexIndicesX, const Array2DInt32& m_pPreviousVertexIndicesX,
const Array2DInt32& m_pPreviousVertexIndicesY, const Array2DInt32& m_pPreviousVertexIndicesY,
const Array2DInt32& m_pPreviousVertexIndicesZ, const Array2DInt32& m_pPreviousVertexIndicesZ,

View File

@ -50,43 +50,51 @@ namespace PolyVox
Mesh(); Mesh();
~Mesh(); ~Mesh();
const std::vector<IndexType>& getIndices(void) const;
uint32_t getNoOfIndices(void) const;
IndexType getNoOfVertices(void) const; IndexType getNoOfVertices(void) const;
const std::vector<VertexType>& getVertices(void) const; const VertexType& getVertex(IndexType index) const;
const Vector3DInt32& getOffset(void) 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 setOffset(const Vector3DInt32& offset);
void addTriangle(IndexType index0, IndexType index1, IndexType index2);
IndexType addVertex(const VertexType& vertex); IndexType addVertex(const VertexType& vertex);
void addTriangle(IndexType index0, IndexType index1, IndexType index2);
void clear(void); void clear(void);
bool isEmpty(void) const; bool isEmpty(void) const;
void removeUnusedVertices(void); void removeUnusedVertices(void);
Vector3DInt32 m_offset; private:
std::vector<IndexType> m_vecIndices;
public:
std::vector<IndexType> m_vecTriangleIndices;
std::vector<VertexType> m_vecVertices; std::vector<VertexType> m_vecVertices;
Vector3DInt32 m_offset;
}; };
template <typename MeshType> 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; Mesh< Vertex< typename MeshType::VertexType::DataType >, typename MeshType::IndexType > decodedMesh;
result.m_vecVertices.resize(mesh.m_vecVertices.size());
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> template <typename VertexType, typename IndexType>
IndexType Mesh<VertexType, IndexType>::getNoOfVertices(void) const IndexType Mesh<VertexType, IndexType>::getNoOfVertices(void) const
{ {
return m_vecVertices.size(); 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> template <typename VertexType, typename IndexType>
const std::vector<VertexType>& Mesh<VertexType, IndexType>::getVertices(void) const const std::vector<VertexType>& Mesh<VertexType, IndexType>::getVertices(void) const
{ {
return m_vecVertices; 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> template <typename VertexType, typename IndexType>
const Vector3DInt32& Mesh<VertexType, IndexType>::getOffset(void) const 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(index1 < m_vecVertices.size(), "Index points at an invalid vertex.");
POLYVOX_ASSERT(index2 < 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_vecIndices.push_back(index0);
m_vecTriangleIndices.push_back(index1); m_vecIndices.push_back(index1);
m_vecTriangleIndices.push_back(index2); m_vecIndices.push_back(index2);
} }
template <typename VertexType, typename IndexType> template <typename VertexType, typename IndexType>
@ -96,7 +120,7 @@ namespace PolyVox
void Mesh<VertexType, IndexType>::clear(void) void Mesh<VertexType, IndexType>::clear(void)
{ {
m_vecVertices.clear(); m_vecVertices.clear();
m_vecTriangleIndices.clear(); m_vecIndices.clear();
} }
template <typename VertexType, typename IndexType> template <typename VertexType, typename IndexType>
@ -111,9 +135,9 @@ namespace PolyVox
std::vector<bool> isVertexUsed(m_vecVertices.size()); std::vector<bool> isVertexUsed(m_vecVertices.size());
std::fill(isVertexUsed.begin(), isVertexUsed.end(), false); 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; isVertexUsed[v] = true;
} }
@ -131,9 +155,9 @@ namespace PolyVox
m_vecVertices.resize(noOfUsedVertices); 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 // MarchingCubesSurfaceExtractor
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
template<typename VolumeType, typename Controller> class MarchingCubesSurfaceExtractor; template<typename VolumeType, typename MeshType, typename ControllerType> class MarchingCubesSurfaceExtractor;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// MarchingCubesVertex // MarchingCubesVertex
@ -142,7 +142,8 @@ namespace PolyVox
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Mesh // Mesh
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
template <typename VertexType, typename IndexType = uint32_t> class Mesh; typedef uint32_t DefaultIndexType;
template <typename VertexType, typename IndexType = DefaultIndexType> class Mesh;
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Pager // Pager

View File

@ -26,6 +26,7 @@ freely, subject to the following restrictions:
#include "PolyVoxCore/Density.h" #include "PolyVoxCore/Density.h"
#include "PolyVoxCore/Material.h" #include "PolyVoxCore/Material.h"
#include "PolyVoxCore/MaterialDensityPair.h" #include "PolyVoxCore/MaterialDensityPair.h"
#include "PolyVoxCore/RawVolume.h"
#include "PolyVoxCore/SimpleVolume.h" #include "PolyVoxCore/SimpleVolume.h"
#include "PolyVoxCore/CubicSurfaceExtractor.h" #include "PolyVoxCore/CubicSurfaceExtractor.h"
@ -33,157 +34,148 @@ freely, subject to the following restrictions:
using namespace PolyVox; using namespace PolyVox;
template<typename _VoxelType>
// These 'writeDensityValueToVoxel' functions provide a unified interface for writting densities to primative and class voxel types. class CustomIsQuadNeeded
// 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)
{ {
voxel = valueToWrite; public:
} typedef _VoxelType VoxelType;
template<> bool operator()(VoxelType back, VoxelType front, VoxelType& materialToUse)
void writeDensityValueToVoxel(int valueToWrite, Density8& voxel) {
{ // Not a useful test - it just does something different
voxel.setDensity(valueToWrite); // to the DefaultIsQuadNeeded so we can check it compiles.
} if ((back > 1) && (front <= 1))
{
materialToUse = static_cast<VoxelType>(back);
return true;
}
else
{
return false;
}
}
};
template<> // Runs the surface extractor for a given type.
void writeDensityValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel) template <typename VolumeType>
VolumeType* createAndFillVolumeWithNoise(int32_t iVolumeSideLength, typename VolumeType::VoxelType minValue, typename VolumeType::VoxelType maxValue)
{ {
voxel.setDensity(valueToWrite); //Create empty volume
} VolumeType* volData = new VolumeType(Region(Vector3DInt32(0, 0, 0), Vector3DInt32(iVolumeSideLength - 1, iVolumeSideLength - 1, iVolumeSideLength - 1)));
template<typename VoxelType> // Seed generator for consistency between runs.
void writeMaterialValueToVoxel(int valueToWrite, VoxelType& voxel) srand(12345);
{
//Most types don't have a material
return;
}
template<> //Fill the volume with data
void writeMaterialValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel) for (int32_t z = 0; z < iVolumeSideLength; z++)
{ {
voxel.setMaterial(valueToWrite); 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));
}
}
}
}
return volData;
} }
// Runs the surface extractor for a given type. // Runs the surface extractor for a given type.
template <typename VoxelType> template <typename VolumeType>
uint32_t testForType(void) VolumeType* createAndFillVolumeRealistic(int32_t iVolumeSideLength)
{ {
const int32_t uVolumeSideLength = 256;
//Create empty volume //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 //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; volData->setVoxelAt(x, y, z, 0);
writeDensityValueToVoxel<VoxelType>(100, voxelValue); }
writeMaterialValueToVoxel<VoxelType>(42, voxelValue); else
volData.setVoxelAt(x, y, z, voxelValue); {
volData->setVoxelAt(x, y, z, 1);
} }
} }
} }
} }
uint32_t uTotalVertices = 0; return volData;
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)
{
for (int32_t y = 0; y < uVolumeSideLength; y += uRegionSideLength)
{
for (int32_t x = 0; x < uVolumeSideLength; x += uRegionSideLength)
{
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();
}
}
}
// 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::testBehaviour()
{ {
/*const static uint32_t uExpectedVertices = 6624; // Test with default mesh and contoller types.
const static uint32_t uExpectedIndices = 9936; auto uint8Vol = createAndFillVolumeWithNoise< SimpleVolume<uint8_t> >(32, 0, 2);
const static uint32_t uMaterialToCheck = 3000; auto uint8Mesh = extractCubicMesh(uint8Vol, uint8Vol->getEnclosingRegion());
const static float fExpectedMaterial = 42.0f; QCOMPARE(uint8Mesh.getNoOfVertices(), uint32_t(57687));
const static uint32_t uIndexToCheck = 2000; QCOMPARE(uint8Mesh.getNoOfIndices(), uint32_t(216234));
const static uint32_t uExpectedIndex = 1334;
Mesh<CubicVertex> mesh;*/ // 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));
/*testForType<int8_t>(mesh); // Test with default controller but user-provided mesh.
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); auto uint32Vol = createAndFillVolumeWithNoise< SimpleVolume<uint32_t> >(32, 0, 2);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); Mesh< CubicVertex< uint32_t >, uint16_t > uint32Mesh;
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); extractCubicMeshCustom(uint32Vol, uint32Vol->getEnclosingRegion(), &uint32Mesh);
QCOMPARE(uint32Mesh.getNoOfVertices(), uint16_t(57687));
QCOMPARE(uint32Mesh.getNoOfIndices(), uint32_t(216234));
testForType<uint8_t>(mesh); // Test with both mesh and controller being provided by the user.
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); auto int32Vol = createAndFillVolumeWithNoise< SimpleVolume<int32_t> >(32, 0, 2);
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); Mesh< CubicVertex< int32_t >, uint16_t > int32Mesh;
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); extractCubicMeshCustom(int32Vol, int32Vol->getEnclosingRegion(), &int32Mesh, CustomIsQuadNeeded<int32_t>());
QCOMPARE(int32Mesh.getNoOfVertices(), uint16_t(29027));
QCOMPARE(int32Mesh.getNoOfIndices(), uint32_t(178356));
}
testForType<int16_t>(mesh); void TestCubicSurfaceExtractor::testEmptyVolumePerformance()
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); {
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); auto emptyVol = createAndFillVolumeWithNoise< SimpleVolume<uint32_t> >(128, 0, 0);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); 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<uint16_t>(mesh); void TestCubicSurfaceExtractor::testRealisticVolumePerformance()
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); {
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); auto realisticVol = createAndFillVolumeRealistic< SimpleVolume<uint32_t> >(128);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); 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<int32_t>(mesh); void TestCubicSurfaceExtractor::testNoiseVolumePerformance()
QCOMPARE(mesh.getNoOfVertices(), uExpectedVertices); {
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); auto noiseVol = createAndFillVolumeWithNoise< SimpleVolume<uint32_t> >(128, 0, 2);
QCOMPARE(mesh.getVertices()[uMaterialToCheck].getMaterial(), fNoMaterial); Mesh< CubicVertex< uint32_t >, uint16_t > noiseMesh;
QBENCHMARK{ extractCubicMeshCustom(noiseVol, Region(32, 32, 32, 63, 63, 63), &noiseMesh); }
testForType<uint32_t>(mesh); QCOMPARE(noiseMesh.getNoOfVertices(), uint16_t(57729));
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>();
}
QCOMPARE(result, uExpectedSumOfVerticesAndIndices);
} }
QTEST_MAIN(TestCubicSurfaceExtractor) QTEST_MAIN(TestCubicSurfaceExtractor)

View File

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

View File

@ -52,12 +52,12 @@ public:
float convertToMaterial(float /*voxel*/) float convertToMaterial(float /*voxel*/)
{ {
return 1; return 1.0f;
} }
float blendMaterials(float /*a*/, float /*b*/, float /*weight*/) float blendMaterials(float /*a*/, float /*b*/, float /*weight*/)
{ {
return 1; return 1.0f;
} }
float getThreshold(void) float getThreshold(void)
@ -75,12 +75,6 @@ void writeDensityValueToVoxel(int valueToWrite, VoxelType& voxel)
voxel = valueToWrite; voxel = valueToWrite;
} }
template<>
void writeDensityValueToVoxel(int valueToWrite, Density8& voxel)
{
voxel.setDensity(valueToWrite);
}
template<> template<>
void writeDensityValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel) void writeDensityValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel)
{ {
@ -100,130 +94,137 @@ void writeMaterialValueToVoxel(int valueToWrite, MaterialDensityPair88& voxel)
voxel.setMaterial(valueToWrite); voxel.setMaterial(valueToWrite);
} }
// Runs the surface extractor for a given type.
template <typename VoxelType> 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 //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 z = 0; z < uVolumeSideLength; z++)
{ {
for (int32_t y = 0; y < uVolumeSideLength; y++) for (int32_t y = 0; y < uVolumeSideLength; y++)
{ {
for (int32_t x = 0; x < uVolumeSideLength; x++) 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; VoxelType voxelValue;
//Create a density field which changes throughout the volume.
writeDensityValueToVoxel<VoxelType>(x + y + z, voxelValue); writeDensityValueToVoxel<VoxelType>(x + y + z, voxelValue);
//Two different materials in two halves of the volume
writeMaterialValueToVoxel<VoxelType>(z > uVolumeSideLength / 2 ? 42 : 79, voxelValue); writeMaterialValueToVoxel<VoxelType>(z > uVolumeSideLength / 2 ? 42 : 79, voxelValue);
volData.setVoxelAt(x, y, z, voxelValue); volData->setVoxelAt(x, y, z, voxelValue);
} }
} }
} }
DefaultMarchingCubesController<VoxelType> controller; return volData;
controller.setThreshold(50);
auto result = extractMarchingCubesMesh(&volData, volData.getEnclosingRegion(), WrapModes::Border, VoxelType(), controller);
return result;
} }
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 //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; float voxelValue = randomFloat(minValue, maxValue);
volData.setVoxelAt(x, y, z, voxelValue); volData->setVoxelAt(x, y, z, voxelValue);
} }
} }
} }
CustomMarchingCubesController controller; return volData;
MarchingCubesSurfaceExtractor< SimpleVolume<float>, CustomMarchingCubesController > extractor(&volData, volData.getEnclosingRegion(), &result, WrapModes::Border, 0, controller);
extractor.execute();
} }
void TestSurfaceExtractor::testExecute() void TestSurfaceExtractor::testBehaviour()
{ {
const static uint32_t uExpectedVertices = 4731; // These tests apply the Marching Cubes surface extractor to volumes of various voxel types. In addition we sometimes make use of custom controllers
const static uint32_t uExpectedIndices = 12810; // and user-provided meshes to make sure these various combinations work as expected.
const static uint32_t uMaterialToCheck = 3000; //
const static float fExpectedData = 1.0f; // 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
const static float fNoMaterial = 1.0f; // 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; // This basic test just uses the default controller and automatically generates a mesh of the appropriate type.
//Run the test for various voxel types. auto uintVol = createAndFillVolume<uint8_t>();
QBENCHMARK { auto uintMesh = extractMarchingCubesMesh(uintVol, uintVol->getEnclosingRegion());
mesh = testForType<int8_t>(); 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(mesh.getNoOfVertices(), uExpectedVertices); QCOMPARE(uintMesh.getIndex(100), uint32_t(44)); // Verifies that we have 32-bit indices
QCOMPARE(mesh.getNoOfIndices(), uExpectedIndices); QCOMPARE(uintMesh.getVertex(100).data, uint8_t(1)); // Not really meaningful for a primative type
QCOMPARE(mesh.getVertices()[uMaterialToCheck].data, static_cast<int8_t>(fExpectedData));
auto mesh1 = testForType<uint8_t>(); // This test makes use of a custom controller
QCOMPARE(mesh1.getNoOfVertices(), uExpectedVertices); auto floatVol = createAndFillVolume<float>();
QCOMPARE(mesh1.getNoOfIndices(), uExpectedIndices); CustomMarchingCubesController floatCustomController;
QCOMPARE(mesh1.getVertices()[uMaterialToCheck].data, static_cast<uint8_t>(fExpectedData)); 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<int16_t>(); // 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
QCOMPARE(mesh2.getNoOfVertices(), uExpectedVertices); // use a default for the second-to-last parameter but noot use a default for the last parameter.
QCOMPARE(mesh2.getNoOfIndices(), uExpectedIndices); auto intVol = createAndFillVolume<int8_t>();
QCOMPARE(mesh2.getVertices()[uMaterialToCheck].data, static_cast<int16_t>(fExpectedData)); 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<uint16_t>(); // This test makes use of a user-provided mesh and also a custom controller.
QCOMPARE(mesh3.getNoOfVertices(), uExpectedVertices); auto doubleVol = createAndFillVolume<double>();
QCOMPARE(mesh3.getNoOfIndices(), uExpectedIndices); CustomMarchingCubesController doubleCustomController;
QCOMPARE(mesh3.getVertices()[uMaterialToCheck].data, static_cast<uint16_t>(fExpectedData)); 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<int32_t>(); // This test ensures the extractor works on a non-primitive voxel type.
QCOMPARE(mesh4.getNoOfVertices(), uExpectedVertices); auto materialVol = createAndFillVolume<MaterialDensityPair88>();
QCOMPARE(mesh4.getNoOfIndices(), uExpectedIndices); auto materialMesh = extractMarchingCubesMesh(materialVol, materialVol->getEnclosingRegion());
QCOMPARE(mesh4.getVertices()[uMaterialToCheck].data, static_cast<int32_t>(fExpectedData)); 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<uint32_t>(); void TestSurfaceExtractor::testEmptyVolumePerformance()
QCOMPARE(mesh5.getNoOfVertices(), uExpectedVertices); {
QCOMPARE(mesh5.getNoOfIndices(), uExpectedIndices); auto emptyVol = createAndFillVolumeWithNoise< SimpleVolume<float> >(128, -2.0f, -1.0f);
QCOMPARE(mesh5.getVertices()[uMaterialToCheck].data, static_cast<uint32_t>(fExpectedData)); 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<float>(); void TestSurfaceExtractor::testNoiseVolumePerformance()
QCOMPARE(mesh6.getNoOfVertices(), uExpectedVertices); {
QCOMPARE(mesh6.getNoOfIndices(), uExpectedIndices); auto noiseVol = createAndFillVolumeWithNoise< SimpleVolume<float> >(128, -1.0f, 1.0f);
QCOMPARE(mesh6.getVertices()[uMaterialToCheck].data, static_cast<float>(fExpectedData)); Mesh< MarchingCubesVertex< float >, uint16_t > noiseMesh;
QBENCHMARK{ extractMarchingCubesMeshCustom(noiseVol, Region(32, 32, 32, 63, 63, 63), &noiseMesh); }
auto mesh7 = testForType<double>(); QCOMPARE(noiseMesh.getNoOfVertices(), uint16_t(48967));
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);*/
} }
QTEST_MAIN(TestSurfaceExtractor) QTEST_MAIN(TestSurfaceExtractor)

View File

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