Split the code which generates vertices and indices for a single cell into a separate function.

This commit is contained in:
David Williams 2015-05-28 23:26:50 +02:00
parent f32bb1d1ed
commit 2fa291d16f

View File

@ -49,92 +49,13 @@ namespace PolyVox
} }
template< typename VolumeType, typename MeshType, typename ControllerType > template< typename VolumeType, typename MeshType, typename ControllerType >
void extractMarchingCubesMeshCustom(VolumeType* volData, Region region, MeshType* result, ControllerType controller) void generateMeshForCell(Region& region, MeshType* result, ControllerType& controller, typename VolumeType::Sampler& sampler, Array<2, Vector3DInt32>& pIndices, Array<2, Vector3DInt32>& pPreviousIndices, uint8_t iCubeIndex, uint32_t uXRegSpace, uint32_t uYRegSpace, uint32_t uZRegSpace, typename ControllerType::DensityType tThreshold)
{
POLYVOX_THROW_IF(result == nullptr, std::invalid_argument, "Provided mesh cannot be null");
Timer timer;
result->clear();
typename ControllerType::DensityType tThreshold = controller.getThreshold();
const uint32_t uRegionWidthInVoxels = region.getWidthInVoxels();
const uint32_t uRegionHeightInVoxels = region.getHeightInVoxels();
const uint32_t uRegionDepthInVoxels = region.getDepthInVoxels();
// No need to clear memory because we only read from elements we have written to.
Array<2, Vector3DInt32> pIndices(uRegionWidthInVoxels, uRegionHeightInVoxels);
Array<2, Vector3DInt32> pPreviousIndices(uRegionWidthInVoxels, uRegionHeightInVoxels);
Array2DUint8 pPreviousSliceBitmask(uRegionWidthInVoxels, uRegionHeightInVoxels);
Array1DUint8 pPreviousRowBitmask(uRegionWidthInVoxels);
uint8_t uPreviousCell = 0;
typename VolumeType::Sampler startOfSlice(volData);
startOfSlice.setPosition(region.getLowerX(), region.getLowerY(), region.getLowerZ());
for (uint32_t uZRegSpace = 0; uZRegSpace < uRegionDepthInVoxels; uZRegSpace++)
{
typename VolumeType::Sampler startOfRow = startOfSlice;
for (uint32_t uYRegSpace = 0; uYRegSpace < uRegionHeightInVoxels; uYRegSpace++)
{
// Copying a sampler which is already pointing at the correct location seems (slightly) faster than
// calling setPosition(). Therefore we make use of 'startOfRow' and 'startOfSlice' to reset the sampler.
typename VolumeType::Sampler sampler = startOfRow;
for (uint32_t uXRegSpace = 0; uXRegSpace < uRegionWidthInVoxels; uXRegSpace++)
{
// Note: In many cases the provided region will be (mostly) empty which means mesh vertices/indices
// are not generated and the only thing that is done for each cell is the computation of iCubeIndex.
// It appears that retriving the voxel value is not so expensive and that it is the bitwise combining
// which actually carries the cost.
//
// If we really need to speed this up more then it may be possible to pack 4 8-bit cell indices into
// a single 32-bit value and then perform the bitwise logic on all four of them at the same time.
// However, this complicates the code and there would still be the cost of packing/unpacking so it's
// not clear if there is really a benefit. It's something to consider in the future.
uint8_t iCubeIndex = 0;
// Four bits of our cube index are obtained by looking at the cube index for
// the previous slice and copying four of those bits into their new positions.
uint8_t iPreviousCubeIndexZ = pPreviousSliceBitmask(uXRegSpace, uYRegSpace);
iPreviousCubeIndexZ >>= 4;
iCubeIndex |= iPreviousCubeIndexZ;
// Two bits of our cube index are obtained by looking at the cube index for
// the previous row and copying two of those bits into their new positions.
uint8_t iPreviousCubeIndexY = pPreviousRowBitmask(uXRegSpace);
iPreviousCubeIndexY &= 204; //204 = 128+64+8+4
iPreviousCubeIndexY >>= 2;
iCubeIndex |= iPreviousCubeIndexY;
// One bit of our cube index are obtained by looking at the cube index for
// the previous cell and copying one of those bits into it's new position.
uint8_t iPreviousCubeIndexX = uPreviousCell;
iPreviousCubeIndexX &= 170; //170 = 128+32+8+2
iPreviousCubeIndexX >>= 1;
iCubeIndex |= iPreviousCubeIndexX;
// The last bit of our cube index is obtained by looking
// at the relevant voxel and comparing it to the threshold
typename VolumeType::VoxelType v111 = sampler.getVoxel();
if (controller.convertToDensity(v111) < tThreshold) iCubeIndex |= 128;
// The current value becomes the previous value, ready for the next iteration.
uPreviousCell = iCubeIndex;
pPreviousRowBitmask(uXRegSpace) = iCubeIndex;
pPreviousSliceBitmask(uXRegSpace, uYRegSpace) = iCubeIndex;
/* Cube is entirely in/out of the surface */
uint16_t uEdge = edgeTable[iCubeIndex];
if (uEdge != 0)
{ {
auto v111 = sampler.getVoxel();
auto v111Density = controller.convertToDensity(v111); auto v111Density = controller.convertToDensity(v111);
const Vector3DFloat n000 = computeCentralDifferenceGradient(sampler, controller); const Vector3DFloat n000 = computeCentralDifferenceGradient(sampler, controller);
uint16_t uEdge = edgeTable[iCubeIndex];
/* Find the vertices where the surface intersects the cube */ /* Find the vertices where the surface intersects the cube */
if ((uEdge & 64) && (uXRegSpace > 0)) if ((uEdge & 64) && (uXRegSpace > 0))
{ {
@ -311,6 +232,99 @@ namespace PolyVox
} }
} // For each triangle } // For each triangle
} }
}
template< typename VolumeType, typename MeshType, typename ControllerType >
void extractMarchingCubesMeshCustom(VolumeType* volData, Region region, MeshType* result, ControllerType controller)
{
POLYVOX_THROW_IF(result == nullptr, std::invalid_argument, "Provided mesh cannot be null");
Timer timer;
result->clear();
typename ControllerType::DensityType tThreshold = controller.getThreshold();
const uint32_t uRegionWidthInVoxels = region.getWidthInVoxels();
const uint32_t uRegionHeightInVoxels = region.getHeightInVoxels();
const uint32_t uRegionDepthInVoxels = region.getDepthInVoxels();
// No need to clear memory because we only read from elements we have written to.
Array<2, Vector3DInt32> pIndices(uRegionWidthInVoxels, uRegionHeightInVoxels);
Array<2, Vector3DInt32> pPreviousIndices(uRegionWidthInVoxels, uRegionHeightInVoxels);
Array2DUint8 pPreviousSliceBitmask(uRegionWidthInVoxels, uRegionHeightInVoxels);
Array1DUint8 pPreviousRowBitmask(uRegionWidthInVoxels);
uint8_t uPreviousCell = 0;
typename VolumeType::Sampler startOfSlice(volData);
startOfSlice.setPosition(region.getLowerX(), region.getLowerY(), region.getLowerZ());
for (uint32_t uZRegSpace = 0; uZRegSpace < uRegionDepthInVoxels; uZRegSpace++)
{
typename VolumeType::Sampler startOfRow = startOfSlice;
for (uint32_t uYRegSpace = 0; uYRegSpace < uRegionHeightInVoxels; uYRegSpace++)
{
// Copying a sampler which is already pointing at the correct location seems (slightly) faster than
// calling setPosition(). Therefore we make use of 'startOfRow' and 'startOfSlice' to reset the sampler.
typename VolumeType::Sampler sampler = startOfRow;
for (uint32_t uXRegSpace = 0; uXRegSpace < uRegionWidthInVoxels; uXRegSpace++)
{
// Note: In many cases the provided region will be (mostly) empty which means mesh vertices/indices
// are not generated and the only thing that is done for each cell is the computation of iCubeIndex.
// It appears that retriving the voxel value is not so expensive and that it is the bitwise combining
// which actually carries the cost.
//
// If we really need to speed this up more then it may be possible to pack 4 8-bit cell indices into
// a single 32-bit value and then perform the bitwise logic on all four of them at the same time.
// However, this complicates the code and there would still be the cost of packing/unpacking so it's
// not clear if there is really a benefit. It's something to consider in the future.
uint8_t iCubeIndex = 0;
// Four bits of our cube index are obtained by looking at the cube index for
// the previous slice and copying four of those bits into their new positions.
uint8_t iPreviousCubeIndexZ = pPreviousSliceBitmask(uXRegSpace, uYRegSpace);
iPreviousCubeIndexZ >>= 4;
iCubeIndex |= iPreviousCubeIndexZ;
// Two bits of our cube index are obtained by looking at the cube index for
// the previous row and copying two of those bits into their new positions.
uint8_t iPreviousCubeIndexY = pPreviousRowBitmask(uXRegSpace);
iPreviousCubeIndexY &= 204; //204 = 128+64+8+4
iPreviousCubeIndexY >>= 2;
iCubeIndex |= iPreviousCubeIndexY;
// One bit of our cube index are obtained by looking at the cube index for
// the previous cell and copying one of those bits into it's new position.
uint8_t iPreviousCubeIndexX = uPreviousCell;
iPreviousCubeIndexX &= 170; //170 = 128+32+8+2
iPreviousCubeIndexX >>= 1;
iCubeIndex |= iPreviousCubeIndexX;
// The last bit of our cube index is obtained by looking
// at the relevant voxel and comparing it to the threshold
typename VolumeType::VoxelType v111 = sampler.getVoxel();
if (controller.convertToDensity(v111) < tThreshold) iCubeIndex |= 128;
// The current value becomes the previous value, ready for the next iteration.
uPreviousCell = iCubeIndex;
pPreviousRowBitmask(uXRegSpace) = iCubeIndex;
pPreviousSliceBitmask(uXRegSpace, uYRegSpace) = iCubeIndex;
/* Cube is entirely in/out of the surface */
if (edgeTable[iCubeIndex] != 0)
{
// This is a rather ugly function call and appears to have some cost compared to inlining the code.
// As a result the case when a cell contains vertices/indices is slightly slower, but the (more common)
// case where a cell is empty is slightly faster, probably because the main loop is a lot more compact.
// Having a seperate function will also make it easier to profile in the future and see whether empty or
// occupied cells are really the bottleneck. The large number of parameters is messy though, so it
// would be nice to reduce these if we can work out how.
generateMeshForCell<VolumeType, MeshType, ControllerType>(region, result, controller,
sampler, pIndices, pPreviousIndices, iCubeIndex, uXRegSpace, uYRegSpace, uZRegSpace, tThreshold);
} // For each cell } // For each cell
sampler.movePositiveX(); sampler.movePositiveX();
} // For X } // For X