polyvox/include/PolyVox/DualContouringSurfaceExtractor.inl

360 lines
15 KiB
C++

/*******************************************************************************
Copyright (c) 2014 David Williams and Matt Williams
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*******************************************************************************/
#include "PolyVoxCore/SurfaceMesh.h"
#include "PolyVoxCore/VertexTypes.h"
#include "Impl/Timer.h"
#include "Impl/QEF.h"
#include <type_traits>
//BUG We will get duplucation of edges if the surface is along region boundaries
namespace PolyVox
{
namespace
{
template<typename VoxelType>
struct EdgeData
{
EdgeData() : intersects(false) {}
Vector3DFloat normal;
float fraction; ///<fraction (0.0-1.0) along the edge in the positive direction that the intersection happens
bool intersects;
};
template<typename VoxelType>
struct CellData
{
EdgeData<VoxelType> edges[3];
uint32_t vertexIndex;
};
template<typename VoxelType, typename ThresholdType>
EdgeData<VoxelType> calculateEdge(const VoxelType& vA, const VoxelType& vB, const Vector3DFloat& gA, const Vector3DFloat& gB, const ThresholdType& threshold)
{
EdgeData<VoxelType> edge;
edge.fraction = static_cast<float>(vA - threshold) / static_cast<float>(vA - vB);
if(std::min(vA,vB) <= threshold && std::max(vA,vB) > threshold)
{
edge.intersects = true;
}
else
{
edge.intersects = false;
return edge;
}
edge.normal = (gA * edge.fraction + gB * (1.0f-edge.fraction));
if(edge.normal.lengthSquared() != 0.0f)
{
edge.normal.normalise();
}
return edge;
}
template<typename VoxelType>
PositionMaterialNormal computeVertex(EdgeData<VoxelType>* edges[12])
{
Vector3DFloat massPoint{0,0,0}; //The average of the intersection vertices
Vector3DFloat vertices[12];
vertices[0] = {edges[0]->fraction, 0, 0};
vertices[1] = {0, edges[1]->fraction, 0};
vertices[2] = {0, 0, edges[2]->fraction};
vertices[3] = {1, edges[3]->fraction, 0};
vertices[4] = {1, 0, edges[4]->fraction};
vertices[5] = {0, 1, edges[5]->fraction};
vertices[6] = {edges[6]->fraction, 1, 0};
vertices[7] = {edges[7]->fraction, 0, 1};
vertices[8] = {0, edges[8]->fraction, 1};
vertices[9] = {1, 1, edges[9]->fraction};
vertices[10] = {1, edges[10]->fraction, 1};
vertices[11] = {edges[11]->fraction, 1, 1};
int numIntersections = 0;
for(int i = 0; i < 12; ++i)
{
if(!edges[i]->intersects)
{
continue;
}
++numIntersections;
massPoint += vertices[i];
}
massPoint /= numIntersections; //Make the average
Vector3DFloat cellVertexNormal{0,0,0};
double matrix[12][3];
double vector[12];
int rows = 0;
for(int i = 0; i < 12; ++i)
{
if(!edges[i]->intersects)
{
continue;
}
Vector3DFloat normal = edges[i]->normal;
matrix[rows][0] = normal.getX();
matrix[rows][1] = normal.getY();
matrix[rows][2] = normal.getZ();
const Vector3DFloat product = normal * (vertices[i] - massPoint);
vector[rows] = product.getX() + product.getY() + product.getZ();
cellVertexNormal += normal;
++rows;
}
const auto& vertexPosition = evaluateQEF(matrix, vector, rows) + massPoint;
POLYVOX_ASSERT(vertexPosition.getX() >= -0.01 && vertexPosition.getY() >= -0.01 && vertexPosition.getZ() >= -0.01 && vertexPosition.getX() <= 1.01 && vertexPosition.getY() <= 1.01 && vertexPosition.getZ() <= 1.01, "Vertex is outside unit cell"); //0.01 to give a little leniency
if(cellVertexNormal.lengthSquared() != 0.0f)
{
cellVertexNormal.normalise();
}
return {vertexPosition, cellVertexNormal, 0};
}
uint32_t convert(uint32_t x, uint32_t y, uint32_t z, uint32_t X, uint32_t Y)
{
return z*Y*X+y*X+x;
}
}
template<typename VolumeType, typename ControllerType>
SurfaceMesh<PositionMaterialNormal> dualContouringSurfaceExtractor(VolumeType* volData, const Region& region, const ControllerType& controller)
{
static_assert(std::is_signed<typename ControllerType::DensityType>::value, "Voxel type must be signed");
const auto threshold = controller.getThreshold();
//Timer timer;
Timer totalTimer;
const auto regionXDimension = region.getDimensionsInVoxels().getX();
const auto regionYDimension = region.getDimensionsInVoxels().getY();
const auto regionZDimension = region.getDimensionsInVoxels().getZ();
const auto gradientRegionXDimension = regionXDimension+2;
const auto gradientRegionYDimension = regionYDimension+2;
const auto gradientRegionZDimension = regionZDimension+2;
std::vector<std::pair<const typename VolumeType::VoxelType, const Vector3DFloat>> gradients;
gradients.reserve(gradientRegionXDimension * gradientRegionYDimension * gradientRegionZDimension);
typename VolumeType::Sampler volSampler{volData};
volSampler.setPosition(region.getLowerCorner() - Vector3DInt32{1,1,1});
volSampler.setWrapMode(WrapMode::Border, -100.0); // -100.0 is well below the threshold
const auto lowerCornerX = region.getLowerCorner().getX();
const auto lowerCornerY = region.getLowerCorner().getZ();
const auto lowerCornerZ = region.getLowerCorner().getX();
//logTrace() << "Setup took " << timer.elapsedTimeInMilliSeconds();
//timer.start();
for(int32_t z = 0; z < gradientRegionZDimension; z++)
{
volSampler.setPosition(lowerCornerX-1, lowerCornerY-1, lowerCornerZ+z-1); //Reset x and y and increment z
for(int32_t y = 0; y < gradientRegionYDimension; y++)
{
volSampler.setPosition(lowerCornerX-1, lowerCornerY+y-1, lowerCornerZ+z-1); //Reset x and increment y (z remains the same)
for(int32_t x = 0; x < gradientRegionXDimension; x++)
{
volSampler.movePositiveX(); //Increment x
const auto& voxel = controller.convertToDensity(volSampler.getVoxel());
const auto& voxel1px = controller.convertToDensity(volSampler.peekVoxel1px0py0pz());
const auto& voxel1py = controller.convertToDensity(volSampler.peekVoxel0px1py0pz());
const auto& voxel1pz = controller.convertToDensity(volSampler.peekVoxel0px0py1pz());
const auto& voxel1nx = controller.convertToDensity(volSampler.peekVoxel1nx0py0pz());
const auto& voxel1ny = controller.convertToDensity(volSampler.peekVoxel0px1ny0pz());
const auto& voxel1nz = controller.convertToDensity(volSampler.peekVoxel0px0py1nz());
gradients.emplace_back(voxel, Vector3DFloat(voxel1nx - voxel1px, voxel1ny - voxel1py, voxel1nz - voxel1pz));
}
}
}
//logTrace() << "Gradients took " << timer.elapsedTimeInMilliSeconds();
//timer.start();
const auto cellRegionXDimension = regionXDimension+2;
const auto cellRegionYDimension = regionYDimension+2;
const auto cellRegionZDimension = regionZDimension+2;
std::vector<CellData<typename VolumeType::VoxelType>> cells;
cells.reserve(cellRegionXDimension * cellRegionYDimension * cellRegionZDimension);
for(int32_t cellZ = 0; cellZ < cellRegionZDimension; cellZ++)
{
for(int32_t cellY = 0; cellY < cellRegionYDimension; cellY++)
{
for(int32_t cellX = 0; cellX < cellRegionXDimension; cellX++)
{
//For each cell, calculate the edge intersection points and normals
const auto& g000 = gradients[convert(cellX, cellY, cellZ, cellRegionXDimension, cellRegionYDimension)];
//For the last columns/rows, only calculate the interior edge
if(cellX < cellRegionXDimension-1 && cellY < cellRegionYDimension-1 && cellZ < cellRegionZDimension-1) //This is the main bulk
{
const auto& g100 = gradients[convert(cellX+1, cellY, cellZ, cellRegionXDimension, cellRegionYDimension)];
const auto& g010 = gradients[convert(cellX, cellY+1, cellZ, cellRegionXDimension, cellRegionYDimension)];
const auto& g001 = gradients[convert(cellX, cellY, cellZ+1, cellRegionXDimension, cellRegionYDimension)];
cells.push_back({calculateEdge(g000.first, g100.first, g000.second, g100.second, threshold), calculateEdge(g000.first, g010.first, g000.second, g010.second, threshold), calculateEdge(g000.first, g001.first, g000.second, g001.second, threshold)});
}
else if(cellX == cellRegionXDimension-1 || cellY == cellRegionYDimension-1 || cellZ == cellRegionZDimension-1) //This is the three far edges and the far corner
{
cells.push_back({}); //Default and empty
}
else if(cellX == cellRegionXDimension-1) //Far x side
{
const auto& g100 = gradients[convert(cellX+1, cellY, cellZ, cellRegionXDimension, cellRegionYDimension)];
cells.push_back({calculateEdge(g000.first, g100.first, g000.second, g100.second, threshold), EdgeData<typename VolumeType::VoxelType>(), EdgeData<typename VolumeType::VoxelType>()});
}
else if(cellY == cellRegionYDimension-1) //Far y side
{
const auto& g010 = gradients[convert(cellX+1, cellY, cellZ, cellRegionXDimension, cellRegionYDimension)];
cells.push_back({EdgeData<typename VolumeType::VoxelType>(), calculateEdge(g000.first, g010.first, g000.second, g010.second, threshold), EdgeData<typename VolumeType::VoxelType>()});
}
else if(cellZ == cellRegionZDimension-1) //Far z side
{
const auto& g001 = gradients[convert(cellX+1, cellY, cellZ, cellRegionXDimension, cellRegionYDimension)];
cells.push_back({EdgeData<typename VolumeType::VoxelType>(), EdgeData<typename VolumeType::VoxelType>(), calculateEdge(g000.first, g001.first, g000.second, g001.second, threshold)});
}
}
}
}
//logTrace() << "Edges took " << timer.elapsedTimeInMilliSeconds();
//timer.start();
EdgeData<typename VolumeType::VoxelType>* edges[12]; //Create this now but it will be overwritten for each cell
SurfaceMesh<PositionMaterialNormal> mesh;
for(int32_t cellZ = 0; cellZ < cellRegionZDimension; cellZ++)
{
for(int32_t cellY = 0; cellY < cellRegionYDimension; cellY++)
{
for(int32_t cellX = 0; cellX < cellRegionXDimension; cellX++)
{
if(cellZ >= 1 && cellY >= 1 && cellX >= 1)
{
//After the first rows and columns are done, start calculating vertex positions
const int32_t cellXVertex = cellX-1;
const int32_t cellYVertex = cellY-1;
const int32_t cellZVertex = cellZ-1;
auto& cell = cells[convert(cellXVertex, cellYVertex, cellZVertex, cellRegionXDimension, cellRegionYDimension)];
edges[0] = &cell.edges[0];
edges[1] = &cell.edges[1];
edges[2] = &cell.edges[2];
edges[3] = &cells[convert(cellXVertex+1, cellYVertex, cellZVertex, cellRegionXDimension, cellRegionYDimension)].edges[1];
edges[4] = &cells[convert(cellXVertex+1, cellYVertex, cellZVertex, cellRegionXDimension, cellRegionYDimension)].edges[2];
edges[5] = &cells[convert(cellXVertex, cellYVertex+1, cellZVertex, cellRegionXDimension, cellRegionYDimension)].edges[2];
edges[6] = &cells[convert(cellXVertex, cellYVertex+1, cellZVertex, cellRegionXDimension, cellRegionYDimension)].edges[0];
edges[7] = &cells[convert(cellXVertex, cellYVertex, cellZVertex+1, cellRegionXDimension, cellRegionYDimension)].edges[0];
edges[8] = &cells[convert(cellXVertex, cellYVertex, cellZVertex+1, cellRegionXDimension, cellRegionYDimension)].edges[1];
edges[9] = &cells[convert(cellXVertex+1, cellYVertex+1, cellZVertex, cellRegionXDimension, cellRegionYDimension)].edges[2];
edges[10] = &cells[convert(cellXVertex+1, cellYVertex, cellZVertex+1, cellRegionXDimension, cellRegionYDimension)].edges[1];
edges[11] = &cells[convert(cellXVertex, cellYVertex+1, cellZVertex+1, cellRegionXDimension, cellRegionYDimension)].edges[0];
if(edges[0]->intersects || edges[1]->intersects || edges[2]->intersects || edges[3]->intersects || edges[4]->intersects || edges[5]->intersects || edges[6]->intersects || edges[7]->intersects || edges[8]->intersects || edges[9]->intersects || edges[10]->intersects || edges[11]->intersects) //'if' Maybe not needed?
{
auto vertex = computeVertex(edges);
vertex.setPosition({vertex.getPosition().getX()+cellXVertex, vertex.getPosition().getY()+cellYVertex, vertex.getPosition().getZ()+cellZVertex});
cell.vertexIndex = mesh.addVertex(vertex);
if(cellZVertex >= 1 && cellYVertex >= 1 && cellXVertex >= 1)
{
//Once the second rows and colums are done, start connecting up edges
if(cell.edges[0].intersects)
{
const auto& v1 = cells[convert(cellXVertex, cellYVertex-1, cellZVertex, cellRegionXDimension, cellRegionYDimension)];
const auto& v2 = cells[convert(cellXVertex, cellYVertex, cellZVertex-1, cellRegionXDimension, cellRegionYDimension)];
const auto& v3 = cells[convert(cellXVertex, cellYVertex-1, cellZVertex-1, cellRegionXDimension, cellRegionYDimension)];
mesh.addTriangle(cell.vertexIndex, v1.vertexIndex, v2.vertexIndex);
mesh.addTriangle(v3.vertexIndex, v2.vertexIndex, v1.vertexIndex);
}
if(cell.edges[1].intersects)
{
const auto& v1 = cells[convert(cellXVertex-1, cellYVertex, cellZVertex, cellRegionXDimension, cellRegionYDimension)];
const auto& v2 = cells[convert(cellXVertex, cellYVertex, cellZVertex-1, cellRegionXDimension, cellRegionYDimension)];
const auto& v3 = cells[convert(cellXVertex-1, cellYVertex, cellZVertex-1, cellRegionXDimension, cellRegionYDimension)];
mesh.addTriangle(cell.vertexIndex, v1.vertexIndex, v2.vertexIndex);
mesh.addTriangle(v3.vertexIndex, v2.vertexIndex, v1.vertexIndex);
}
if(cell.edges[2].intersects)
{
const auto& v1 = cells[convert(cellXVertex-1, cellYVertex, cellZVertex, cellRegionXDimension, cellRegionYDimension)];
const auto& v2 = cells[convert(cellXVertex, cellYVertex-1, cellZVertex, cellRegionXDimension, cellRegionYDimension)];
const auto& v3 = cells[convert(cellXVertex-1, cellYVertex-1, cellZVertex, cellRegionXDimension, cellRegionYDimension)];
mesh.addTriangle(cell.vertexIndex, v1.vertexIndex, v2.vertexIndex);
mesh.addTriangle(v3.vertexIndex, v2.vertexIndex, v1.vertexIndex);
}
}
}
}
}
}
}
//logTrace() << "Vertices and quads took " << timer.elapsedTimeInMilliSeconds();
//timer.start();
logTrace() << "Dual contouring surface extraction took " << totalTimer.elapsedTimeInMilliSeconds() << "ms (Region size = " << region.getWidthInVoxels() << "x" << region.getHeightInVoxels() << "x" << region.getDepthInVoxels() << ")";
logTrace() << mesh.getNoOfVertices();
return mesh;
}
}