diff --git a/library/PolyVoxCore/include/Volume.h b/library/PolyVoxCore/include/Volume.h index e3e137cf..a9b1a816 100644 --- a/library/PolyVoxCore/include/Volume.h +++ b/library/PolyVoxCore/include/Volume.h @@ -36,6 +36,73 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. namespace PolyVox { + ///The Volume class provides a memory efficient method of storing voxel data while also allowing fast access and modification. + /******************************************************************************* + A Volume is essentially a '3D image' in which each element (voxel) is identified + by a three dimensional (x,y,z) coordinate, rather than the two dimensional (x,y) + coordinate which is used to identify an element (pixel) in a normal image. Within + PolyVox, the Volume class is used to store and manipulate our data before we extract + our SurfacePatch's from it. + + Data Representaion - feel free to skip + If stored carelessly, volume data can take up a huge amount of memory. For example, a + volume of dimensions 1024x1024x1024 with 1 byte per voxel will require 1GB of memory + if stored in an uncompressed form. Natuarally our Volume class is much more efficient + than this and it is worth understanding (at least at a high level) the approach + which is used. + + Essentially, the Volume class stores its data as a collection of blocks. Each + of these block is much smaller than the whole volume, for example a typical size + might be 32x32x32 voxels (though is is configurable by the user). In this case, + a 256x512x1024 volume would contain 8x16x32 = 4096 blocks. However, it is unlikely that + all these blocks actually have to be stored because usually there are duplicates + in which case common data can be shared. + + Identifying duplicate blocks is in general a difficult task which involves looking at pairs + of blocks and comparing all the voxels. This is a time consuming task which is not amiable + to being performed when the volume is being modified in real time. However, there are two + specific scenarios which are easily spotted and which PolyVox uses to identify block + sharing opportunities. + + -# Homogeneous blocks (those which contain just a single voxel value) are easy to + spot and fairly common becuase volumes often contain large homogeous regions. Any time + you change the value of a voxel you have potentially made the block which contains + it homogeneous. PolyVox does not check the homogeneity immediatly as this would slow + down the process of modifying voxels, but you can use the tidyUpMemory() function + to check for and remove duplicate homogeneous regions whenever you have spare + processing time. + + -# Copying a volume naturally means that all the voxels in the second voluem are + the same as the first. Therefore volume copying is a relatively fast operation in + which all the blocks in the second volume simply reference the first volume. Future + modifications to either volume will, of course, cause the blocks to become unshared. + + Other advantages of breaking the volume down into blocks include enhancing data locality + (i.e. voxels which are spatially near to each other are also likely to be near in + memory) and the ability to load larger volumes as no large contiguous areas of + memory are needed. However, these advantages are more transparent to user code + so we will not dwell on them here. + + Usage + Volumes are constructed by passing the desired width height and depth to the + constructor. Note that for speed reasons only values which are a power of two + are permitted for these sidelengths. + + Access to specific voxels is provided by the getVoxelAt() and setVoxelAt fuctions. + Each of these has two forms so that voxels can be identified by integer triples + or by Vector3DUint16's. + + The tidyUpMemory() function should normally be called after you first populate + the volume with data, and then at periodic intervals as the volume is modified. + However, you don't actually have to call it at all. See the functions + documentation for further details. + + One further important point of note is that this class is templatised on the voxel + type. This allows you to store volumes of data types you might not normally expect, + for example theOpenGL example 'abuses' this class to store a 3D grid of pointers. + However, it is not guarentted that all functionality works correctly with non-integer + voxel types. + *******************************************************************************/ template class Volume { @@ -43,18 +110,24 @@ namespace PolyVox friend class VolumeIterator; public: + ///Constructor Volume(uint16_t uWidth, uint16_t uHeight, uint16_t uDepth, uint16_t uBlockSideLength = 64); - //Volume(const Volume& rhs); + ///Destructor ~Volume(); - //Volume& operator=(const Volume& rhs); - + ///Gets a Region representing the extents of the Volume. Region getEnclosingRegion(void) const; + ///Gets the width of the volume in voxels. uint16_t getWidth(void) const; + ///Gets the height of the volume in voxels. uint16_t getHeight(void) const; + ///Gets the depth of the volume in voxels. uint16_t getDepth(void) const; + ///Gets the length of the longest side in voxels uint16_t getLongestSideLength(void) const; + ///Gets the length of the shortest side in voxels uint16_t getShortestSideLength(void) const; + ///Gets the length of the diagonal in voxels float getDiagonalLength(void) const; VoxelType getVoxelAt(uint16_t uXPos, uint16_t uYPos, uint16_t uZPos) const; VoxelType getVoxelAt(const Vector3DUint16& v3dPos) const; @@ -76,6 +149,7 @@ namespace PolyVox //shared. A call to shared_ptr::unique() from within setVoxel was not sufficient as weak_ptr's did //not contribute to the reference count. Instead we store shared_ptr's here, and check if they //are used by anyone else (i.e are non-unique) when we tidy the volume. + //FIXME - How do we handle sharing between two volumes with different block sizes?! static std::map > > m_pHomogenousBlock; uint32_t m_uNoOfBlocksInVolume; diff --git a/library/PolyVoxCore/include/Volume.inl b/library/PolyVoxCore/include/Volume.inl index 82b5b04d..399308ac 100644 --- a/library/PolyVoxCore/include/Volume.inl +++ b/library/PolyVoxCore/include/Volume.inl @@ -35,16 +35,27 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. namespace PolyVox { #pragma region Constructors/Destructors + /******************************************************************************* + 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. Specifying + '0' for the block side length will cause the blocks to be as large as possible, + which will basically be the length of the shortest side. Accept the default if + you are not sure what to choose here. + *******************************************************************************/ template Volume::Volume(uint16_t uWidth, uint16_t uHeight, uint16_t uDepth, uint16_t uBlockSideLength) :m_pBlocks(0) ,m_uCurrentBlockForTidying(0) { - //A values of zero for a block side length is a special value to indicate that the block - //side length should simply be made as large as possible. This can be useful if you are - //creating only a small volume which doesn't need to be broken down into many blocks. This - //'largest possible block size' will be equal to the shortest volume dimension, as a volume - //dimension can never be less than a block side length. + //A values of zero for a block side length is a special value to indicate + //that the block side length should simply be made as large as possible. if(uBlockSideLength == 0) { uBlockSideLength = (std::min)((std::min)(uWidth,uHeight),uDepth); @@ -119,12 +130,9 @@ namespace PolyVox m_fDiagonalLength = sqrtf(static_cast(m_uWidth * m_uWidth + m_uHeight * m_uHeight + m_uDepth * m_uDepth)); } - /*template - Volume::Volume(const Volume& rhs) - { - *this = rhs; - }*/ - + /******************************************************************************* + Destroys the volume and frees any blocks which are not in use by other volumes. + *******************************************************************************/ template Volume::~Volume() { @@ -132,55 +140,84 @@ namespace PolyVox #pragma endregion #pragma region Operators - /*template - Volume& Volume::operator=(const Volume& rhs) - { - - }*/ #pragma endregion #pragma region Getters + /******************************************************************************* + 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 - uint16_t Volume::getDepth(void) const + uint16_t Volume::getWidth(void) const { - return m_uDepth; - } - - template - float Volume::getDiagonalLength(void) const - { - return m_fDiagonalLength; + return m_uWidth; } + /******************************************************************************* + \return The height of the volume in voxels + \sa getWidth(), getDepth() + *******************************************************************************/ template uint16_t Volume::getHeight(void) const { return m_uHeight; } + /******************************************************************************* + \return The depth of the volume in voxels + \sa getWidth(), getHeight() + *******************************************************************************/ template - uint16_t Volume::getLongestSideLength(void) const + uint16_t Volume::getDepth(void) const { - return m_uLongestSideLength; + 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 uint16_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 - uint16_t Volume::getWidth(void) const + uint16_t Volume::getLongestSideLength(void) const { - return m_uWidth; - } + 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; + } template VoxelType Volume::getVoxelAt(uint16_t uXPos, uint16_t uYPos, uint16_t uZPos) const