diff --git a/examples/OpenGL/OpenGLWidget.cpp b/examples/OpenGL/OpenGLWidget.cpp index 64279d1c..63192bc6 100644 --- a/examples/OpenGL/OpenGLWidget.cpp +++ b/examples/OpenGL/OpenGLWidget.cpp @@ -58,9 +58,9 @@ void OpenGLWidget::setVolume(PolyVox::Volume* volData) //If we have any volume data then generate the new surface patches. if(m_volData != 0) { - m_uVolumeWidthInRegions = g_uVolumeSideLength / m_uRegionSideLength; - m_uVolumeHeightInRegions = g_uVolumeSideLength / m_uRegionSideLength; - m_uVolumeDepthInRegions = g_uVolumeSideLength / m_uRegionSideLength; + m_uVolumeWidthInRegions = volData->getWidth() / m_uRegionSideLength; + m_uVolumeHeightInRegions = volData->getHeight() / m_uRegionSideLength; + m_uVolumeDepthInRegions = volData->getDepth() / m_uRegionSideLength; //Our volume is broken down into cuboid regions, and we create one mesh for each region. //This three-level for loop iterates over each region. @@ -180,9 +180,8 @@ void OpenGLWidget::paintGL() glMatrixMode(GL_MODELVIEW); // Select The Model View Matrix glLoadIdentity(); // Reset The Current Modelview Matrix - float diag_len = sqrtf(static_cast(255 * 255 * 3)); //Moves the camera back so we can see the volume - glTranslatef(0.0f, 0.0f, -diag_len); + glTranslatef(0.0f, 0.0f, -m_volData->getDiagonalLength()); glRotatef(m_xRotation, 1.0f, 0.0f, 0.0f); glRotatef(m_yRotation, 0.0f, 1.0f, 0.0f); @@ -249,8 +248,7 @@ void OpenGLWidget::setupProjectionMatrix(void) glMatrixMode(GL_PROJECTION); glLoadIdentity(); - float diag_len = sqrtf(static_cast(255 * 255 * 3)); - float frustumSize = diag_len / 2.0f; + float frustumSize = m_volData->getDiagonalLength() / 2.0f; float aspect = static_cast(width()) / static_cast(height()); glOrtho(frustumSize*aspect, -frustumSize*aspect, frustumSize, -frustumSize, 1.0, 5000); diff --git a/examples/OpenGL/Shapes.cpp b/examples/OpenGL/Shapes.cpp index fdeec22e..023f2d54 100644 --- a/examples/OpenGL/Shapes.cpp +++ b/examples/OpenGL/Shapes.cpp @@ -27,19 +27,17 @@ freely, subject to the following restrictions: using namespace PolyVox; -const uint16_t g_uVolumeSideLength = 128; - void createSphereInVolume(Volume& volData, float fRadius, uint8_t uValue) { //This vector hold the position of the center of the volume - Vector3DFloat v3dVolCenter( g_uVolumeSideLength / 2, g_uVolumeSideLength / 2, g_uVolumeSideLength / 2); + Vector3DFloat v3dVolCenter(volData.getWidth() / 2, volData.getHeight() / 2, volData.getDepth() / 2); //This three-level for loop iterates over every voxel in the volume - for (int z = 0; z < g_uVolumeSideLength ; z++) + for (int z = 0; z < volData.getWidth(); z++) { - for (int y = 0; y < g_uVolumeSideLength; y++) + for (int y = 0; y < volData.getHeight(); y++) { - for (int x = 0; x < g_uVolumeSideLength; x++) + for (int x = 0; x < volData.getDepth(); x++) { //Store our current position as a vector... Vector3DFloat v3dCurrentPos(x,y,z); diff --git a/examples/OpenGL/main.cpp b/examples/OpenGL/main.cpp index 9129092f..7e5e8fab 100644 --- a/examples/OpenGL/main.cpp +++ b/examples/OpenGL/main.cpp @@ -72,7 +72,7 @@ void exampleLog(string message, int severity) int main(int argc, char *argv[]) { logHandler = &exampleLog; - Volume volData; + Volume volData(g_uVolumeSideLength, g_uVolumeSideLength, g_uVolumeSideLength); //Make our volume contain a sphere in the center. int32_t minPos = 0; diff --git a/library/PolyVoxCore/include/Region.h b/library/PolyVoxCore/include/Region.h index 081e2679..a4af688a 100644 --- a/library/PolyVoxCore/include/Region.h +++ b/library/PolyVoxCore/include/Region.h @@ -44,6 +44,7 @@ namespace PolyVox void setLowerCorner(const Vector3DInt32& v3dLowerCorner); void setUpperCorner(const Vector3DInt32& v3dUpperCorner); + void setToMaxSize(void); bool containsPoint(const Vector3DFloat& pos, float boundary = 0.0f) const; bool containsPoint(const Vector3DInt32& pos, uint8_t boundary = 0) const; diff --git a/library/PolyVoxCore/include/Volume.h b/library/PolyVoxCore/include/Volume.h index e5ef8fff..cdeb8566 100644 --- a/library/PolyVoxCore/include/Volume.h +++ b/library/PolyVoxCore/include/Volume.h @@ -32,7 +32,6 @@ freely, subject to the following restrictions: #include #include #include -#include namespace PolyVox { @@ -124,9 +123,27 @@ namespace PolyVox public: /// Constructor Volume(uint16_t uBlockSideLength = 32); + /// Constructor + Volume(int32_t uWidth, int32_t uHeight, int32_t uDepth, uint16_t uBlockSideLength = 32); /// Destructor ~Volume(); + /// Gets the value used for voxels which are outside the volume + VoxelType getBorderValue(void) const; + /// Gets a Region representing the extents of the Volume. + Region getEnclosingRegion(void) const; + /// Gets the width of the volume in voxels. + int32_t getWidth(void) const; + /// Gets the height of the volume in voxels. + int32_t getHeight(void) const; + /// Gets the depth of the volume in voxels. + int32_t getDepth(void) const; + /// Gets the length of the longest side in voxels + int32_t getLongestSideLength(void) const; + /// Gets the length of the shortest side in voxels + int32_t getShortestSideLength(void) const; + /// Gets the length of the diagonal in voxels + float getDiagonalLength(void) const; /// Gets a voxel by x,y,z position VoxelType getVoxelAt(int32_t uXPos, int32_t uYPos, int32_t uZPos) const; /// Gets a voxel by 3D vector position @@ -148,6 +165,7 @@ namespace PolyVox uint32_t calculateSizeInBytes(void); /// Resizes the volume to the specified dimensions void resize(uint16_t uBlockSideLength = 32); + void resize(int32_t uWidth, int32_t uHeight, int32_t uDepth, uint16_t uBlockSideLength); /// gets called when a new region is allocated and needs to be filled /// NOTE: accessing ANY voxels outside this region during the process of this function /// is absolutely unsafe @@ -184,8 +202,40 @@ namespace PolyVox //the VolumeIterator can do it's usual pointer arithmetic without needing to know it's gone outside the volume. VoxelType* m_pUncompressedBorderData; + /*int32_t m_uMinX; + int32_t m_uMinY; + int32_t m_uMinZ; + + int32_t m_uMaxX; + int32_t m_uMaxY; + int32_t m_uMaxZ;*/ + + Region m_regValidRegion; + + /*int32_t m_uBlockMinX; + int32_t m_uBlockMinY; + int32_t m_uBlockMinZ; + + int32_t m_uBlockMaxX; + int32_t m_uBlockMaxY; + int32_t m_uBlockMaxZ;*/ + + Region m_regValidRegionInBlocks; + + int32_t m_uWidthInBlocks; + int32_t m_uHeightInBlocks; + int32_t m_uDepthInBlocks; + + int32_t m_uWidth; + int32_t m_uHeight; + int32_t m_uDepth; + uint8_t m_uBlockSideLengthPower; uint16_t m_uBlockSideLength; + + int32_t m_uLongestSideLength; + int32_t m_uShortestSideLength; + float m_fDiagonalLength; }; //Some handy typedefs diff --git a/library/PolyVoxCore/include/Volume.inl b/library/PolyVoxCore/include/Volume.inl index fecef294..fe195d66 100644 --- a/library/PolyVoxCore/include/Volume.inl +++ b/library/PolyVoxCore/include/Volume.inl @@ -59,10 +59,48 @@ namespace PolyVox { setBlockCacheSize(m_uMaxUncompressedBlockCacheSize); + m_regValidRegion.setToMaxSize(); + + m_regValidRegionInBlocks.setLowerCorner(m_regValidRegion.getLowerCorner() / static_cast(uBlockSideLength)); + m_regValidRegionInBlocks.setUpperCorner(m_regValidRegion.getUpperCorner() / static_cast(uBlockSideLength)); + //Create a volume of the right size. resize(uBlockSideLength); } + //////////////////////////////////////////////////////////////////////////////// + /// Builds a volume of the desired dimensions + /// \param uWidth The desired width in voxels. This must be a power of two. + /// \param uHeight The desired height in voxels. This must be a power of two. + /// \param uDepth The desired depth in voxels. This must be a power of two. + /// \param uBlockSideLength The size of the blocks which make up the volume. Small + /// blocks are more likely to be homogeneous (so more easily shared) and have better + /// cache behaviour. However, there is a memory overhead per block so if they are + /// not shared it could actually be less efficient (this will depend on the data). + /// The size of the volume may also be a factor when choosing block size. Accept + /// the default if you are not sure what to choose here. + //////////////////////////////////////////////////////////////////////////////// + template + Volume::Volume(int32_t uWidth, int32_t uHeight, int32_t uDepth, uint16_t uBlockSideLength) + :m_uTimestamper(0) + ,m_uMaxUncompressedBlockCacheSize(256) + ,m_uBlockSideLength(uBlockSideLength) + ,m_pUncompressedBorderData(0) + ,m_uMaxBlocksLoaded(4096) + ,m_v3dLastAccessedBlockPos((std::numeric_limits::max)(), (std::numeric_limits::max)(), (std::numeric_limits::max)()) //An invalid index + { + setBlockCacheSize(m_uMaxUncompressedBlockCacheSize); + + m_regValidRegion.setLowerCorner(Vector3DInt32(0,0,0)); + m_regValidRegion.setUpperCorner(Vector3DInt32(uWidth - 1,uHeight - 1,uDepth - 1)); + + m_regValidRegionInBlocks.setLowerCorner(m_regValidRegion.getLowerCorner() / static_cast(uBlockSideLength)); + m_regValidRegionInBlocks.setUpperCorner(m_regValidRegion.getUpperCorner() / static_cast(uBlockSideLength)); + + //Create a volume of the right size. + resize(uWidth, uHeight, uDepth, uBlockSideLength); + } + //////////////////////////////////////////////////////////////////////////////// /// Destroys the volume and frees any blocks which are not in use by other volumes. //////////////////////////////////////////////////////////////////////////////// @@ -75,6 +113,95 @@ namespace PolyVox } } + //////////////////////////////////////////////////////////////////////////////// + /// The border value is returned whenever an atempt is made to read a voxel which + /// is outside the extents of the volume. + /// \return The value used for voxels outside of the volume + //////////////////////////////////////////////////////////////////////////////// + template + VoxelType Volume::getBorderValue(void) const + { + /*Block* pUncompressedBorderBlock = getUncompressedBlock(const_cast*>(&m_pBorderBlock)); + return pUncompressedBorderBlock->getVoxelAt(0,0,0);*/ + return *m_pUncompressedBorderData; + } + + //////////////////////////////////////////////////////////////////////////////// + /// The result will always have a lower corner at (0,0,0) and an upper corner at one + /// less than the side length. For example, if a volume has dimensions 256x512x1024 + /// then the upper corner of the enclosing region will be at (255,511,1023). + /// \return A Region representing the extent of the volume. + //////////////////////////////////////////////////////////////////////////////// + template + Region Volume::getEnclosingRegion(void) const + { + return Region(Vector3DInt32(0,0,0), Vector3DInt32(m_uWidth-1,m_uHeight-1,m_uDepth-1)); + } + + //////////////////////////////////////////////////////////////////////////////// + /// \return The width of the volume in voxels + /// \sa getHeight(), getDepth() + //////////////////////////////////////////////////////////////////////////////// + template + int32_t Volume::getWidth(void) const + { + return m_uWidth; + } + + //////////////////////////////////////////////////////////////////////////////// + /// \return The height of the volume in voxels + /// \sa getWidth(), getDepth() + //////////////////////////////////////////////////////////////////////////////// + template + int32_t Volume::getHeight(void) const + { + return m_uHeight; + } + + //////////////////////////////////////////////////////////////////////////////// + /// \return The depth of the volume in voxels + /// \sa getWidth(), getHeight() + //////////////////////////////////////////////////////////////////////////////// + template + int32_t Volume::getDepth(void) const + { + return m_uDepth; + } + + //////////////////////////////////////////////////////////////////////////////// + /// \return The length of the shortest side in voxels. For example, if a volume has + /// dimensions 256x512x1024 this function will return 256. + /// \sa getLongestSideLength(), getDiagonalLength() + //////////////////////////////////////////////////////////////////////////////// + template + int32_t Volume::getShortestSideLength(void) const + { + return m_uShortestSideLength; + } + + //////////////////////////////////////////////////////////////////////////////// + /// \return The length of the longest side in voxels. For example, if a volume has + /// dimensions 256x512x1024 this function will return 1024. + /// \sa getShortestSideLength(), getDiagonalLength() + //////////////////////////////////////////////////////////////////////////////// + template + int32_t Volume::getLongestSideLength(void) const + { + return m_uLongestSideLength; + } + + //////////////////////////////////////////////////////////////////////////////// + /// \return The length of the diagonal in voxels. For example, if a volume has + /// dimensions 256x512x1024 this function will return sqrt(256*256+512*512+1024*1024) + /// = 1173.139. This value is computed on volume creation so retrieving it is fast. + /// \sa getShortestSideLength(), getLongestSideLength() + //////////////////////////////////////////////////////////////////////////////// + template + float Volume::getDiagonalLength(void) const + { + return m_fDiagonalLength; + } + //////////////////////////////////////////////////////////////////////////////// /// \param uXPos the \c x position of the voxel /// \param uYPos the \c y position of the voxel @@ -145,6 +272,17 @@ namespace PolyVox m_uMaxBlocksLoaded = uMaxBlocks; } + //////////////////////////////////////////////////////////////////////////////// + /// \param tBorder The value to use for voxels outside the volume. + //////////////////////////////////////////////////////////////////////////////// + template + void Volume::setBorderValue(const VoxelType& tBorder) + { + /*Block* pUncompressedBorderBlock = getUncompressedBlock(&m_pBorderBlock); + return pUncompressedBorderBlock->fill(tBorder);*/ + std::fill(m_pUncompressedBorderData, m_pUncompressedBorderData + m_uBlockSideLength * m_uBlockSideLength * m_uBlockSideLength, tBorder); + } + //////////////////////////////////////////////////////////////////////////////// /// \param uXPos the \c x position of the voxel /// \param uYPos the \c y position of the voxel @@ -230,6 +368,99 @@ namespace PolyVox m_uBlockSideLengthPower = logBase2(m_uBlockSideLength); } + //////////////////////////////////////////////////////////////////////////////// + /// Note: Calling this function will destroy all existing data in the volume. + /// \param uWidth The desired width in voxels. This must be a power of two. + /// \param uHeight The desired height in voxels. This must be a power of two. + /// \param uDepth The desired depth in voxels. This must be a power of two. + /// \param uBlockSideLength The size of the blocks which make up the volume. Small + /// blocks are more likely to be homogeneous (so more easily shared) and have better + /// cache behaviour. However, there is a memory overhead per block so if they are + /// not shared it could actually be less efficient (this will depend on the data). + /// The size of the volume may also be a factor when choosing block size. Accept + /// the default if you are not sure what to choose here. + //////////////////////////////////////////////////////////////////////////////// + template + void Volume::resize(int32_t uWidth, int32_t uHeight, int32_t uDepth, uint16_t uBlockSideLength) + { + //Debug mode validation + assert(uBlockSideLength > 0); + assert(isPowerOf2(uBlockSideLength)); + assert(uBlockSideLength <= uWidth); + assert(uBlockSideLength <= uHeight); + assert(uBlockSideLength <= uDepth); + assert(0 < uWidth); + assert(0 < uHeight); + assert(0 < uDepth); + + //Release mode validation + if(uBlockSideLength == 0) + { + throw std::invalid_argument("Block side length cannot be zero."); + } + if(!isPowerOf2(uBlockSideLength)) + { + throw std::invalid_argument("Block side length must be a power of two."); + } + if(uBlockSideLength > uWidth) + { + throw std::invalid_argument("Block side length cannot be greater than volume width."); + } + if(uBlockSideLength > uHeight) + { + throw std::invalid_argument("Block side length cannot be greater than volume height."); + } + if(uBlockSideLength > uDepth) + { + throw std::invalid_argument("Block side length cannot be greater than volume depth."); + } + if(0 >= uWidth) + { + throw std::invalid_argument("Volume width cannot be smaller than 1."); + } + if(0 >= uHeight) + { + throw std::invalid_argument("Volume height cannot be smaller than 1."); + } + if(0 >= uDepth) + { + throw std::invalid_argument("Volume depth cannot be smaller than 1."); + } + + //Clear the previous data + m_pBlocks.clear(); + m_pUncompressedTimestamps.clear(); + + //Compute the volume side lengths + m_uWidth = uWidth; + m_uHeight = uHeight; + m_uDepth = uDepth; + + //Compute the block side length + m_uBlockSideLength = uBlockSideLength; + m_uBlockSideLengthPower = logBase2(m_uBlockSideLength); + + //Compute the side lengths in blocks + m_uWidthInBlocks = m_uWidth / m_uBlockSideLength; + m_uHeightInBlocks = m_uHeight / m_uBlockSideLength; + m_uDepthInBlocks = m_uDepth / m_uBlockSideLength; + + //Clear the previous data + m_pBlocks.clear(); + m_pUncompressedTimestamps.clear(); + + m_pUncompressedTimestamps.resize(m_uMaxUncompressedBlockCacheSize, 0); + + //Create the border block + m_pUncompressedBorderData = new VoxelType[m_uBlockSideLength * m_uBlockSideLength * m_uBlockSideLength]; + std::fill(m_pUncompressedBorderData, m_pUncompressedBorderData + m_uBlockSideLength * m_uBlockSideLength * m_uBlockSideLength, VoxelType()); + + //Other properties we might find useful later + m_uLongestSideLength = (std::max)((std::max)(m_uWidth,m_uHeight),m_uDepth); + m_uShortestSideLength = (std::min)((std::min)(m_uWidth,m_uHeight),m_uDepth); + m_fDiagonalLength = sqrtf(static_cast(m_uWidth * m_uWidth + m_uHeight * m_uHeight + m_uDepth * m_uDepth)); + } + template void Volume::eraseBlock(typename std::map >::iterator itBlock) const { diff --git a/library/PolyVoxCore/include/VolumeSampler.inl b/library/PolyVoxCore/include/VolumeSampler.inl index c5fc778d..6a4e3d3f 100644 --- a/library/PolyVoxCore/include/VolumeSampler.inl +++ b/library/PolyVoxCore/include/VolumeSampler.inl @@ -153,9 +153,23 @@ namespace PolyVox uYPosInBlock * mVolume->m_uBlockSideLength + uZPosInBlock * mVolume->m_uBlockSideLength * mVolume->m_uBlockSideLength; - Block* pUncompressedCurrentBlock = mVolume->getUncompressedBlock(uXBlock, uYBlock, uZBlock); + //if((uXBlock < mVolume->m_uWidthInBlocks) && (uYBlock < mVolume->m_uHeightInBlocks) && (uZBlock < mVolume->m_uDepthInBlocks) && (uXBlock >= 0) && (uYBlock >= 0) && (uZBlock >=0)) + //if((uXBlock <= mVolume->m_uBlockMaxX) && (uYBlock <= mVolume->m_uBlockMaxY) && (uZBlock <= mVolume->m_uBlockMaxZ) && (uXBlock >= mVolume->m_uBlockMinX) && (uYBlock >= mVolume->m_uBlockMinY) && (uZBlock >= mVolume->m_uBlockMinZ)) + if(mVolume->m_regValidRegionInBlocks.containsPoint(Vector3DInt32(uXBlock, uYBlock, uZBlock))) + { + /*const uint32_t uBlockIndexInVolume = uXBlock + + uYBlock * mVolume->m_uWidthInBlocks + + uZBlock * mVolume->m_uWidthInBlocks * mVolume->m_uHeightInBlocks; + const Block& currentBlock = mVolume->m_pBlocks[uBlockIndexInVolume];*/ - mCurrentVoxel = pUncompressedCurrentBlock->m_tUncompressedData + uVoxelIndexInBlock; + Block* pUncompressedCurrentBlock = mVolume->getUncompressedBlock(uXBlock, uYBlock, uZBlock); + + mCurrentVoxel = pUncompressedCurrentBlock->m_tUncompressedData + uVoxelIndexInBlock; + } + else + { + mCurrentVoxel = mVolume->m_pUncompressedBorderData + uVoxelIndexInBlock; + } } template diff --git a/library/PolyVoxCore/source/GradientEstimators.cpp b/library/PolyVoxCore/source/GradientEstimators.cpp index ad264a2b..ea94da05 100644 --- a/library/PolyVoxCore/source/GradientEstimators.cpp +++ b/library/PolyVoxCore/source/GradientEstimators.cpp @@ -41,15 +41,22 @@ namespace PolyVox VolumeSampler volIter(volumeData); - Vector3DFloat v3dGradient = computeNormal(volumeData, v3dPos, normalGenerationMethod); + //Check all corners are within the volume, allowing a boundary for gradient estimation + bool lowerCornerInside = volumeData->getEnclosingRegion().containsPoint(v3dFloor,2); + bool upperCornerInside = volumeData->getEnclosingRegion().containsPoint(v3dFloor+Vector3DInt32(1,1,1),2); - if(v3dGradient.lengthSquared() > 0.0001) + if(lowerCornerInside && upperCornerInside) //If this test fails the vertex will be left as it was { - //If we got a normal of significant length then update it. - //Otherwise leave it as it was (should be the 'simple' version) - v3dGradient.normalise(); - iterSurfaceVertex->setNormal(v3dGradient); - } + Vector3DFloat v3dGradient = computeNormal(volumeData, v3dPos, normalGenerationMethod); + + if(v3dGradient.lengthSquared() > 0.0001) + { + //If we got a normal of significant length then update it. + //Otherwise leave it as it was (should be the 'simple' version) + v3dGradient.normalise(); + iterSurfaceVertex->setNormal(v3dGradient); + } + } //(lowerCornerInside && upperCornerInside) ++iterSurfaceVertex; } } diff --git a/library/PolyVoxCore/source/Region.cpp b/library/PolyVoxCore/source/Region.cpp index aaffd252..3c0cb174 100644 --- a/library/PolyVoxCore/source/Region.cpp +++ b/library/PolyVoxCore/source/Region.cpp @@ -57,6 +57,14 @@ namespace PolyVox m_v3dUpperCorner = v3dUpperCorner; } + void Region::setToMaxSize(void) + { + int32_t iMin = (std::numeric_limits::min)(); + int32_t iMax = (std::numeric_limits::max)(); + m_v3dLowerCorner = Vector3DInt32(iMin, iMin,iMin); + m_v3dUpperCorner = Vector3DInt32(iMax, iMax,iMax); + } + bool Region::containsPoint(const Vector3DFloat& pos, float boundary) const { return (pos.getX() <= m_v3dUpperCorner.getX() - boundary) diff --git a/library/PolyVoxCore/source/VoxelFilters.cpp b/library/PolyVoxCore/source/VoxelFilters.cpp index a2c6a968..572ab666 100644 --- a/library/PolyVoxCore/source/VoxelFilters.cpp +++ b/library/PolyVoxCore/source/VoxelFilters.cpp @@ -29,6 +29,13 @@ namespace PolyVox { float computeSmoothedVoxel(VolumeSampler& volIter) { + assert(volIter.getPosX() >= 1); + assert(volIter.getPosY() >= 1); + assert(volIter.getPosZ() >= 1); + assert(volIter.getPosX() <= volIter.getVolume()->getWidth() - 2); + assert(volIter.getPosY() <= volIter.getVolume()->getHeight() - 2); + assert(volIter.getPosZ() <= volIter.getVolume()->getDepth() - 2); + float sum = 0.0; if(volIter.peekVoxel1nx1ny1nz() != 0) sum += 1.0f;