diff --git a/include/PolyVox/MarchingCubesSurfaceExtractor.h b/include/PolyVox/MarchingCubesSurfaceExtractor.h index f703e414..ccd4cb82 100644 --- a/include/PolyVox/MarchingCubesSurfaceExtractor.h +++ b/include/PolyVox/MarchingCubesSurfaceExtractor.h @@ -29,13 +29,15 @@ #include "Impl/PlatformDefinitions.h" #include "Array.h" -#include "BaseVolume.h" //For wrap modes... should move these? -#include "Mesh.h" #include "DefaultMarchingCubesController.h" +#include "Mesh.h" #include "Vertex.h" namespace PolyVox { + /// A specialised vertex format which encodes the data from the Marching Cubes algorithm in a very + /// compact way. You will probably want to use the decodeVertex() function to turn it into a regular + /// Vertex for rendering, but advanced users can also decode it on the GPU (see PolyVox examples). template struct MarchingCubesVertex { @@ -57,121 +59,17 @@ namespace PolyVox //template //using MarchingCubesMesh = Mesh< MarchingCubesVertex, IndexType >; - /// Decodes a position from a MarchingCubesVertex - inline Vector3DFloat decodePosition(const Vector3DUint16& encodedPosition) - { - Vector3DFloat result(encodedPosition.getX(), encodedPosition.getY(), encodedPosition.getZ()); - result *= (1.0f / 256.0f); // Division is compile-time constant - return result; - } - - inline uint16_t encodeNormal(const Vector3DFloat& normal) - { - // The first part of this function is based off the code in Listing 1 of http://jcgt.org/published/0003/02/01/ - // It was rewritten in C++ and is restructued for the CPU rather than the GPU. - - // Get the input components - float vx = normal.getX(); - float vy = normal.getY(); - float vz = normal.getZ(); - - // Project the sphere onto the octahedron, and then onto the xy plane - float px = vx * (1.0f / (std::abs(vx) + std::abs(vy) + std::abs(vz))); - float py = vy * (1.0f / (std::abs(vx) + std::abs(vy) + std::abs(vz))); - - // Reflect the folds of the lower hemisphere over the diagonals. - if (vz <= 0.0f) - { - float refx = ((1.0f - std::abs(py)) * (px >= 0.0f ? +1.0f : -1.0f)); - float refy = ((1.0f - std::abs(px)) * (py >= 0.0f ? +1.0f : -1.0f)); - px = refx; - py = refy; - } - - // The next part was not given in the paper. We map our two - // 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.0f) * 127.5f; - py = (py + 1.0f) * 127.5f; - - // Convert to uints - uint16_t resultX = static_cast(px + 0.5f); - uint16_t resultY = static_cast(py + 0.5f); - - // Make sure only the lower bits are set. Probably - // not necessary but we're just being careful really. - resultX &= 0xFF; - resultY &= 0xFF; - - // Contatenate the bytes and return the result. - return (resultX << 8) | resultY; - } - - inline Vector3DFloat decodeNormal(const uint16_t& encodedNormal) - { - // Extract the two bytes from the uint16_t. - uint16_t ux = (encodedNormal >> 8) & 0xFF; - uint16_t uy = (encodedNormal)& 0xFF; - - // Convert to floats in the range [-1.0f, +1.0f]. - float ex = ux / 127.5f - 1.0f; - float ey = uy / 127.5f - 1.0f; - - // Reconstruct the origninal vector. This is a C++ implementation - // of Listing 2 of http://jcgt.org/published/0003/02/01/ - float vx = ex; - float vy = ey; - float vz = 1.0f - std::abs(ex) - std::abs(ey); - - if (vz < 0.0f) - { - float refX = ((1.0f - std::abs(vy)) * (vx >= 0.0f ? +1.0f : -1.0f)); - float refY = ((1.0f - std::abs(vx)) * (vy >= 0.0f ? +1.0f : -1.0f)); - vx = refX; - vy = refY; - } - - // Normalise and return the result. - Vector3DFloat v(vx, vy, vz); - v.normalise(); - return v; - } - /// Decodes a MarchingCubesVertex by converting it into a regular Vertex which can then be directly used for rendering. template - Vertex decodeVertex(const MarchingCubesVertex& marchingCubesVertex) - { - Vertex result; - result.position = decodePosition(marchingCubesVertex.encodedPosition); - result.normal = decodeNormal(marchingCubesVertex.encodedNormal); - result.data = marchingCubesVertex.data; // Data is not encoded - return result; - } + Vertex decodeVertex(const MarchingCubesVertex& marchingCubesVertex); - // 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. + // Generates a mesh from the voxel data using the Marching Cubes algorithm. + template< typename VolumeType, typename ControllerType = DefaultMarchingCubesController > + Mesh > extractMarchingCubesMesh(VolumeType* volData, Region region, ControllerType controller = ControllerType()); + + // Generates a mesh from the voxel data using the Marching Cubes algorithm, placing the result into a user-provided Mesh. template< typename VolumeType, typename MeshType, typename ControllerType = DefaultMarchingCubesController > void extractMarchingCubesMeshCustom(VolumeType* volData, Region region, MeshType* result, ControllerType controller = ControllerType()); - - template< typename VolumeType, typename ControllerType = DefaultMarchingCubesController > - Mesh > extractMarchingCubesMesh(VolumeType* volData, Region region, ControllerType controller = ControllerType()) - { - Mesh > result; - extractMarchingCubesMeshCustom, DefaultIndexType > >(volData, region, &result, controller); - return result; - } } #include "MarchingCubesSurfaceExtractor.inl" diff --git a/include/PolyVox/MarchingCubesSurfaceExtractor.inl b/include/PolyVox/MarchingCubesSurfaceExtractor.inl index 701fb5d1..adbbdf91 100644 --- a/include/PolyVox/MarchingCubesSurfaceExtractor.inl +++ b/include/PolyVox/MarchingCubesSurfaceExtractor.inl @@ -26,6 +26,104 @@ namespace PolyVox { + //////////////////////////////////////////////////////////////////////////////// + // Vertex encoding/decoding + //////////////////////////////////////////////////////////////////////////////// + + inline Vector3DFloat decodePosition(const Vector3DUint16& encodedPosition) + { + Vector3DFloat result(encodedPosition.getX(), encodedPosition.getY(), encodedPosition.getZ()); + result *= (1.0f / 256.0f); // Division is compile-time constant + return result; + } + + inline uint16_t encodeNormal(const Vector3DFloat& normal) + { + // The first part of this function is based off the code in Listing 1 of http://jcgt.org/published/0003/02/01/ + // It was rewritten in C++ and is restructued for the CPU rather than the GPU. + + // Get the input components + float vx = normal.getX(); + float vy = normal.getY(); + float vz = normal.getZ(); + + // Project the sphere onto the octahedron, and then onto the xy plane + float px = vx * (1.0f / (std::abs(vx) + std::abs(vy) + std::abs(vz))); + float py = vy * (1.0f / (std::abs(vx) + std::abs(vy) + std::abs(vz))); + + // Reflect the folds of the lower hemisphere over the diagonals. + if (vz <= 0.0f) + { + float refx = ((1.0f - std::abs(py)) * (px >= 0.0f ? +1.0f : -1.0f)); + float refy = ((1.0f - std::abs(px)) * (py >= 0.0f ? +1.0f : -1.0f)); + px = refx; + py = refy; + } + + // The next part was not given in the paper. We map our two + // 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.0f) * 127.5f; + py = (py + 1.0f) * 127.5f; + + // Convert to uints + uint16_t resultX = static_cast(px + 0.5f); + uint16_t resultY = static_cast(py + 0.5f); + + // Make sure only the lower bits are set. Probably + // not necessary but we're just being careful really. + resultX &= 0xFF; + resultY &= 0xFF; + + // Contatenate the bytes and return the result. + return (resultX << 8) | resultY; + } + + inline Vector3DFloat decodeNormal(const uint16_t& encodedNormal) + { + // Extract the two bytes from the uint16_t. + uint16_t ux = (encodedNormal >> 8) & 0xFF; + uint16_t uy = (encodedNormal)& 0xFF; + + // Convert to floats in the range [-1.0f, +1.0f]. + float ex = ux / 127.5f - 1.0f; + float ey = uy / 127.5f - 1.0f; + + // Reconstruct the origninal vector. This is a C++ implementation + // of Listing 2 of http://jcgt.org/published/0003/02/01/ + float vx = ex; + float vy = ey; + float vz = 1.0f - std::abs(ex) - std::abs(ey); + + if (vz < 0.0f) + { + float refX = ((1.0f - std::abs(vy)) * (vx >= 0.0f ? +1.0f : -1.0f)); + float refY = ((1.0f - std::abs(vx)) * (vy >= 0.0f ? +1.0f : -1.0f)); + vx = refX; + vy = refY; + } + + // Normalise and return the result. + Vector3DFloat v(vx, vy, vz); + v.normalise(); + return v; + } + + template + Vertex decodeVertex(const MarchingCubesVertex& marchingCubesVertex) + { + Vertex result; + result.position = decodePosition(marchingCubesVertex.encodedPosition); + result.normal = decodeNormal(marchingCubesVertex.encodedNormal); + result.data = marchingCubesVertex.data; // Data is not encoded + return result; + } + + //////////////////////////////////////////////////////////////////////////////// + // Gradient estimation + //////////////////////////////////////////////////////////////////////////////// + template< typename Sampler, typename ControllerType> Vector3DFloat computeCentralDifferenceGradient(const Sampler& volIter, ControllerType& controller) { @@ -140,6 +238,33 @@ namespace PolyVox return Vector3DFloat(-xGrad, -yGrad, -zGrad); } + //////////////////////////////////////////////////////////////////////////////// + // Surface extraction + //////////////////////////////////////////////////////////////////////////////// + + // This is probably the version of Marching Cubes extraction which you will want to use initially, at least + // until you determine you have a need for the extra functionality provied by extractMarchingCubesMeshCustom(). + template< typename VolumeType, typename ControllerType > + Mesh > extractMarchingCubesMesh(VolumeType* volData, Region region, ControllerType controller) + { + Mesh > result; + extractMarchingCubesMeshCustom, DefaultIndexType > >(volData, region, &result, controller); + return result; + } + + // 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 > void extractMarchingCubesMeshCustom(VolumeType* volData, Region region, MeshType* result, ControllerType controller) {