Merge branch 'feature/unclassing' into develop

This commit is contained in:
David Williams 2012-10-05 16:58:51 +02:00
commit 96c3b75804
10 changed files with 162 additions and 428 deletions

View File

@ -17,3 +17,20 @@ Some algothms assume that basic mathematical operations can be applied to voxel
Changes to build system
-----------------------
In order to make the build system easier to use, a number of CMake variables were changed to be more consistent. See :doc:`install` for details on the new variable naming.
Changes to CubicSurfaceExtractor
--------------------------------
The behaviour of the CubicSurfaceExtractor has been changed such that it no longer handles some edge cases. Because each generated quad lies between two voxels it can be unclear which region should 'own' a quad when the two voxels are from different regions. The previous version of the CubicSurfaceExtractor would attempt to handle this automatically, but as a result it was possible to get two quads existing at the same position in space. This can cause problems with transparency and with physics, as well as making it harder to decide which regions need to be updated when a voxel is changed.
The new system simplifies the behaviour of this surface extractor but does require a bit of care on the part of the user. You should be clear on the rules controlling when quads are generated and to which regions they will belong. To aid with this we have significantly improved the API documentation for the CubicSurfaceExtractor so be sure to have a look.
Changes to Raycast
------------------
It's been unclassed (makes sense, removes template parameters).
Switch from std::function to STL approach.
Note functor is passed by reference rather than by value.
Resolved endpoints vs direction confusion
Changes to AmbientOcclusionCalculator
-------------------------------------
It's been unclassed and is now called calculateAmbientOcclusion.

View File

@ -79,8 +79,6 @@ SET(CORE_INC_FILES
include/PolyVoxCore/RawVolumeSampler.inl
include/PolyVoxCore/Raycast.h
include/PolyVoxCore/Raycast.inl
include/PolyVoxCore/RaycastWithCallback.h
include/PolyVoxCore/RaycastWithCallback.inl
include/PolyVoxCore/Region.h
include/PolyVoxCore/SimpleInterface.h
include/PolyVoxCore/SimpleVolume.h

View File

@ -41,32 +41,33 @@ freely, subject to the following restrictions:
namespace PolyVox
{
template<typename VolumeType>
class AmbientOcclusionCalculator
template<typename IsVoxelTransparentCallback>
class AmbientOcclusionCalculatorRaycastCallback
{
public:
AmbientOcclusionCalculator(VolumeType* volInput, Array<3, uint8_t>* arrayResult, Region region, float fRayLength, uint8_t uNoOfSamplesPerOutputElement, polyvox_function<bool(const typename VolumeType::VoxelType& voxel)> funcIsTransparent);
~AmbientOcclusionCalculator();
AmbientOcclusionCalculatorRaycastCallback(IsVoxelTransparentCallback isVoxelTransparentCallback)
{
mIsVoxelTransparentCallback = isVoxelTransparentCallback;
}
void execute(void);
bool operator()(const SimpleVolume<uint8_t>::Sampler& sampler)
{
uint8_t sample = sampler.getVoxel();
bool direct = sample == 0;
bool func = mIsVoxelTransparentCallback(sample);
assert(direct == func);
private:
bool raycastCallback(const typename VolumeType::Sampler& sampler);
return direct;
}
Region m_region;
typename VolumeType::Sampler m_sampVolume;
VolumeType* m_volInput;
Array<3, uint8_t>* m_arrayResult;
float m_fRayLength;
uint8_t m_uNoOfSamplesPerOutputElement;
uint16_t mRandomUnitVectorIndex;
uint16_t mRandomVectorIndex;
uint16_t mIndexIncreament;
polyvox_function<bool(const typename VolumeType::VoxelType& voxel)> m_funcIsTransparent;
IsVoxelTransparentCallback mIsVoxelTransparentCallback;
};
// NOTE: The callback needs to be a functor not a function. I haven't been
// able to work the required template magic to get functions working as well.
template<typename VolumeType, typename IsVoxelTransparentCallback>
void calculateAmbientOcclusion(VolumeType* volInput, Array<3, uint8_t>* arrayResult, Region region, float fRayLength, uint8_t uNoOfSamplesPerOutputElement, IsVoxelTransparentCallback isVoxelTransparentCallback);
}
#include "PolyVoxCore/AmbientOcclusionCalculator.inl"

View File

@ -23,44 +23,32 @@ freely, subject to the following restrictions:
namespace PolyVox
{
template<typename VolumeType>
AmbientOcclusionCalculator<VolumeType>::AmbientOcclusionCalculator(VolumeType* volInput, Array<3, uint8_t>* arrayResult, Region region, float fRayLength, uint8_t uNoOfSamplesPerOutputElement, polyvox_function<bool(const typename VolumeType::VoxelType& voxel)> funcIsTransparent)
:m_region(region)
,m_sampVolume(volInput)
,m_volInput(volInput)
,m_arrayResult(arrayResult)
,m_fRayLength(fRayLength)
,m_uNoOfSamplesPerOutputElement(uNoOfSamplesPerOutputElement)
,mRandomUnitVectorIndex(0) //Although these could be uninitialised, we
,mRandomVectorIndex(0) //initialise for consistant results in the tests.
,m_funcIsTransparent(funcIsTransparent)
template<typename VolumeType, typename IsVoxelTransparentCallback>
void calculateAmbientOcclusion(VolumeType* volInput, Array<3, uint8_t>* arrayResult, Region region, float fRayLength, uint8_t uNoOfSamplesPerOutputElement, IsVoxelTransparentCallback isVoxelTransparentCallback)
{
typename VolumeType::Sampler m_sampVolume(volInput);
uint16_t uRandomUnitVectorIndex = 0;
uint16_t uRandomVectorIndex = 0;
uint16_t uIndexIncreament;
//Make sure that the size of the volume is an exact multiple of the size of the array.
assert(m_volInput->getWidth() % arrayResult->getDimension(0) == 0);
assert(m_volInput->getHeight() % arrayResult->getDimension(1) == 0);
assert(m_volInput->getDepth() % arrayResult->getDimension(2) == 0);
assert(volInput->getWidth() % arrayResult->getDimension(0) == 0);
assert(volInput->getHeight() % arrayResult->getDimension(1) == 0);
assert(volInput->getDepth() % arrayResult->getDimension(2) == 0);
//Our initial indices. It doesn't matter exactly what we set here, but the code below makes
//sure they are different for different regions which helps reduce tiling patterns in the results.
mRandomUnitVectorIndex += m_region.getLowerCorner().getX() + m_region.getLowerCorner().getY() + m_region.getLowerCorner().getZ();
mRandomVectorIndex += m_region.getLowerCorner().getX() + m_region.getLowerCorner().getY() + m_region.getLowerCorner().getZ();
uRandomUnitVectorIndex += region.getLowerCorner().getX() + region.getLowerCorner().getY() + region.getLowerCorner().getZ();
uRandomVectorIndex += region.getLowerCorner().getX() + region.getLowerCorner().getY() + region.getLowerCorner().getZ();
//This value helps us jump around in the array a bit more, so the
//nth 'random' value isn't always followed by the n+1th 'random' value.
mIndexIncreament = 1;
}
uIndexIncreament = 1;
template<typename VolumeType>
AmbientOcclusionCalculator<VolumeType>::~AmbientOcclusionCalculator()
{
}
template<typename VolumeType>
void AmbientOcclusionCalculator<VolumeType>::execute(void)
{
const int iRatioX = m_volInput->getWidth() / m_arrayResult->getDimension(0);
const int iRatioY = m_volInput->getHeight() / m_arrayResult->getDimension(1);
const int iRatioZ = m_volInput->getDepth() / m_arrayResult->getDimension(2);
const int iRatioX = volInput->getWidth() / arrayResult->getDimension(0);
const int iRatioY = volInput->getHeight() / arrayResult->getDimension(1);
const int iRatioZ = volInput->getDepth() / arrayResult->getDimension(2);
const float fRatioX = iRatioX;
const float fRatioY = iRatioY;
@ -74,15 +62,12 @@ namespace PolyVox
const Vector3DFloat v3dOffset(0.5f,0.5f,0.5f);
RaycastResult raycastResult;
Raycast<VolumeType> raycast(m_volInput, Vector3DFloat(0.0f,0.0f,0.0f), Vector3DFloat(1.0f,1.0f,1.0f), raycastResult, polyvox_bind(&PolyVox::AmbientOcclusionCalculator<VolumeType>::raycastCallback, this, std::placeholders::_1));
//This loop iterates over the bottom-lower-left voxel in each of the cells in the output array
for(uint16_t z = m_region.getLowerCorner().getZ(); z <= m_region.getUpperCorner().getZ(); z += iRatioZ)
for(uint16_t z = region.getLowerCorner().getZ(); z <= region.getUpperCorner().getZ(); z += iRatioZ)
{
for(uint16_t y = m_region.getLowerCorner().getY(); y <= m_region.getUpperCorner().getY(); y += iRatioY)
for(uint16_t y = region.getLowerCorner().getY(); y <= region.getUpperCorner().getY(); y += iRatioY)
{
for(uint16_t x = m_region.getLowerCorner().getX(); x <= m_region.getUpperCorner().getX(); x += iRatioX)
for(uint16_t x = region.getLowerCorner().getX(); x <= region.getUpperCorner().getX(); x += iRatioX)
{
//Compute a start position corresponding to
//the centre of the cell in the output array.
@ -93,29 +78,28 @@ namespace PolyVox
//Keep track of how many rays did not hit anything
uint8_t uVisibleDirections = 0;
for(int ct = 0; ct < m_uNoOfSamplesPerOutputElement; ct++)
for(int ct = 0; ct < uNoOfSamplesPerOutputElement; ct++)
{
//We take a random vector with components going from -1 to 1 and scale it to go from -halfRatio to +halfRatio.
//This jitter value moves our sample point from the center of the array cell to somewhere else in the array cell
Vector3DFloat v3dJitter = randomVectors[(mRandomVectorIndex += (++mIndexIncreament)) % 1019]; //Prime number helps avoid repetition on sucessive loops.
Vector3DFloat v3dJitter = randomVectors[(uRandomVectorIndex += (++uIndexIncreament)) % 1019]; //Prime number helps avoid repetition on sucessive loops.
v3dJitter *= v3dHalfRatio;
const Vector3DFloat v3dRayStart = v3dStart + v3dJitter;
Vector3DFloat v3dRayDirection = randomUnitVectors[(mRandomUnitVectorIndex += (++mIndexIncreament)) % 1021]; //Differenct prime number.
v3dRayDirection *= m_fRayLength;
Vector3DFloat v3dRayDirection = randomUnitVectors[(uRandomUnitVectorIndex += (++uIndexIncreament)) % 1021]; //Differenct prime number.
v3dRayDirection *= fRayLength;
raycast.setStart(v3dRayStart);
raycast.setDirection(v3dRayDirection);
raycast.execute();
AmbientOcclusionCalculatorRaycastCallback<IsVoxelTransparentCallback> ambientOcclusionCalculatorRaycastCallback(isVoxelTransparentCallback);
RaycastResult result = raycastWithDirection(volInput, v3dRayStart, v3dRayDirection, ambientOcclusionCalculatorRaycastCallback);
if(raycastResult.foundIntersection == false)
if(result == RaycastResults::Completed)
{
++uVisibleDirections;
}
}
float fVisibility;
if(m_uNoOfSamplesPerOutputElement == 0)
if(uNoOfSamplesPerOutputElement == 0)
{
//The user might request zero samples (I've done this in the past while debugging - I don't want to
//wait for ambient occlusion but I do want as valid result for rendering). Avoid the divide by zero.
@ -123,20 +107,13 @@ namespace PolyVox
}
else
{
fVisibility = static_cast<float>(uVisibleDirections) / static_cast<float>(m_uNoOfSamplesPerOutputElement);
fVisibility = static_cast<float>(uVisibleDirections) / static_cast<float>(uNoOfSamplesPerOutputElement);
assert((fVisibility >= 0.0f) && (fVisibility <= 1.0f));
}
(*m_arrayResult)[z / iRatioZ][y / iRatioY][x / iRatioX] = static_cast<uint8_t>(255.0f * fVisibility);
(*arrayResult)[z / iRatioZ][y / iRatioY][x / iRatioX] = static_cast<uint8_t>(255.0f * fVisibility);
}
}
}
}
template<typename VolumeType>
bool AmbientOcclusionCalculator<VolumeType>::raycastCallback(const typename VolumeType::Sampler& sampler)
{
typename VolumeType::VoxelType voxel = sampler.getVoxel();
return m_funcIsTransparent(voxel);
}
}

View File

@ -28,23 +28,17 @@ freely, subject to the following restrictions:
namespace PolyVox
{
/// Stores the result of a raycast operation.
////////////////////////////////////////////////////////////////////////////////
/// A instance of this structure is passed to a Raycast object, and is filled in
/// as the ray traverses the volume. The 'foundIntersection' field indicates whether
/// the ray hit any solid voxels, and if so the 'intersectionVoxel' field indicates
///the voxel's position
////////////////////////////////////////////////////////////////////////////////
struct RaycastResult
namespace RaycastResults
{
///Indicates whether an intersection was found
bool foundIntersection;
///If an intersection was found then this field holds the intersecting voxel, otherwise it is undefined.
Vector3DInt32 intersectionVoxel;
Vector3DInt32 previousVoxel;
};
enum RaycastResult
{
Completed,
Interupted
};
}
typedef RaycastResults::RaycastResult RaycastResult;
/// The Raycast class can be used to find the fist filled voxel along a given path.
/// OUT OF DATE SINCE UNCLASSING
////////////////////////////////////////////////////////////////////////////////
/// The principle behind raycasting is to fire a 'ray' through the volume and determine
/// what (if anything) that ray hits. This simple test can be used for the purpose of
@ -91,35 +85,12 @@ namespace PolyVox
/// surace extractors. It's behaviour with the Marching Cubes surface extractor has not
/// been tested yet.
////////////////////////////////////////////////////////////////////////////////
template<typename VolumeType>
class Raycast
{
public:
///Constructor
Raycast(VolumeType* volData, const Vector3DFloat& v3dStart, const Vector3DFloat& v3dDirectionAndLength, RaycastResult& result, polyvox_function<bool(const typename VolumeType::Sampler& sampler)> funcIsPassable);
///Sets the start position for the ray.
void setStart(const Vector3DFloat& v3dStart);
///Set the direction for the ray.
void setDirection(const Vector3DFloat& v3dDirectionAndLength);
template<typename VolumeType, typename Callback>
RaycastResult raycastWithEndpoints(VolumeType* volData, const Vector3DFloat& v3dStart, const Vector3DFloat& v3dEnd, Callback& callback);
///Performs the raycast.
void execute();
private:
RaycastResult& m_result;
polyvox_function<bool(const typename VolumeType::Sampler& position)> m_funcIsPassable;
void doRaycast(float x1, float y1, float z1, float x2, float y2, float z2);
VolumeType* m_volData;
typename VolumeType::Sampler m_sampVolume;
Vector3DFloat m_v3dStart;
Vector3DFloat m_v3dDirectionAndLength;
float m_fMaxDistance;
};
template<typename VolumeType, typename Callback>
RaycastResult raycastWithDirection(VolumeType* volData, const Vector3DFloat& v3dStart, const Vector3DFloat& v3dDirectionAndLength, Callback& callback);
}
#include "PolyVoxCore/Raycast.inl"

View File

@ -23,63 +23,6 @@ freely, subject to the following restrictions:
namespace PolyVox
{
////////////////////////////////////////////////////////////////////////////////
/// Builds a Raycast object.
/// \param volData A pointer to the volume through which the ray will be cast.
/// \param v3dStart The starting position of the ray.
/// \param v3dDirectionAndLength The direction of the ray. The length of this vector also
/// represents the length of the ray.
/// \param result An instance of RaycastResult in which the result will be stored.
////////////////////////////////////////////////////////////////////////////////
template<typename VolumeType>
Raycast<VolumeType>::Raycast(VolumeType* volData, const Vector3DFloat& v3dStart, const Vector3DFloat& v3dDirectionAndLength, RaycastResult& result, polyvox_function<bool(const typename VolumeType::Sampler& sampler)> funcIsPassable)
:m_result(result)
,m_funcIsPassable(funcIsPassable)
,m_volData(volData)
,m_sampVolume(volData)
,m_v3dStart(v3dStart)
,m_v3dDirectionAndLength(v3dDirectionAndLength)
{
}
////////////////////////////////////////////////////////////////////////////////
/// \param v3dStart The starting position of the ray.
////////////////////////////////////////////////////////////////////////////////
template<typename VolumeType>
void Raycast<VolumeType>::setStart(const Vector3DFloat& v3dStart)
{
m_v3dStart = v3dStart;
}
////////////////////////////////////////////////////////////////////////////////
/// \param v3dDirectionAndLength The direction of the ray. The length of this vector also
/// represents the length of the ray.
////////////////////////////////////////////////////////////////////////////////
template<typename VolumeType>
void Raycast<VolumeType>::setDirection(const Vector3DFloat& v3dDirectionAndLength)
{
//FIXME: We should add a warning when the ray direction is of length one, as this seems to be a common mistake.
m_v3dDirectionAndLength = v3dDirectionAndLength;
}
////////////////////////////////////////////////////////////////////////////////
/// The result is stored in the RaycastResult instance which was passed to the constructor.
////////////////////////////////////////////////////////////////////////////////
template<typename VolumeType>
void Raycast<VolumeType>::execute(void)
{
//The doRaycast function is assuming that it is iterating over the areas defined between
//voxels. We actually want to define the areas as being centered on voxels (as this is
//what the CubicSurfaceExtractor generates). We add (0.5,0.5,0.5) here to adjust for this.
Vector3DFloat v3dStart = m_v3dStart + Vector3DFloat(0.5f, 0.5f, 0.5f);
//Compute the end point
Vector3DFloat v3dEnd = v3dStart + m_v3dDirectionAndLength;
//Do the raycast
doRaycast(v3dStart.getX(), v3dStart.getY(), v3dStart.getZ(), v3dEnd.getX(), v3dEnd.getY(), v3dEnd.getZ());
}
// This function is based on Christer Ericson's code and description of the 'Uniform Grid Intersection Test' in
// 'Real Time Collision Detection'. The following information from the errata on the book website is also relevent:
//
@ -108,10 +51,22 @@ namespace PolyVox
// page 328. The if-statement that reads "if (ty <= tx && ty <= tz)" has a superfluous condition.
// It should simply read "if (ty <= tz)".
//
// This error was reported by Joey Hammer (PixelActive).
template<typename VolumeType>
void Raycast<VolumeType>::doRaycast(float x1, float y1, float z1, float x2, float y2, float z2)
// This error was reported by Joey Hammer (PixelActive).
template<typename VolumeType, typename Callback>
RaycastResult raycastWithEndpoints(VolumeType* volData, const Vector3DFloat& v3dStart, const Vector3DFloat& v3dEnd, Callback& callback)
{
VolumeType::Sampler sampler(volData);
//The doRaycast function is assuming that it is iterating over the areas defined between
//voxels. We actually want to define the areas as being centered on voxels (as this is
//what the CubicSurfaceExtractor generates). We add 0.5 here to adjust for this.
float x1 = v3dStart.getX() + 0.5f;
float y1 = v3dStart.getY() + 0.5f;
float z1 = v3dStart.getZ() + 0.5f;
float x2 = v3dEnd.getX() + 0.5f;
float y2 = v3dEnd.getY() + 0.5f;
float z2 = v3dEnd.getZ() + 0.5f;
int i = (int)floorf(x1);
int j = (int)floorf(y1);
int k = (int)floorf(z1);
@ -135,18 +90,14 @@ namespace PolyVox
float minz = floorf(z1), maxz = minz + 1.0f;
float tz = ((z1 > z2) ? (z1 - minz) : (maxz - z1)) * deltatz;
m_sampVolume.setPosition(i,j,k);
m_result.previousVoxel = Vector3DInt32(i,j,k);
sampler.setPosition(i,j,k);
for(;;)
{
if(!m_funcIsPassable(m_sampVolume))
if(!callback(sampler))
{
m_result.foundIntersection = true;
m_result.intersectionVoxel = Vector3DInt32(i,j,k);
return;
return RaycastResults::Interupted;
}
m_result.previousVoxel = Vector3DInt32(i,j,k);
if(tx <= ty && tx <= tz)
{
@ -154,30 +105,34 @@ namespace PolyVox
tx += deltatx;
i += di;
if(di == 1) m_sampVolume.movePositiveX();
if(di == -1) m_sampVolume.moveNegativeX();
if(di == 1) sampler.movePositiveX();
if(di == -1) sampler.moveNegativeX();
} else if (ty <= tz)
{
if(j == jend) break;
ty += deltaty;
j += dj;
if(dj == 1) m_sampVolume.movePositiveY();
if(dj == -1) m_sampVolume.moveNegativeY();
if(dj == 1) sampler.movePositiveY();
if(dj == -1) sampler.moveNegativeY();
} else
{
if(k == kend) break;
tz += deltatz;
k += dk;
if(dk == 1) m_sampVolume.movePositiveZ();
if(dk == -1) m_sampVolume.moveNegativeZ();
if(dk == 1) sampler.movePositiveZ();
if(dk == -1) sampler.moveNegativeZ();
}
}
//Didn't hit anything
m_result.foundIntersection = false;
m_result.intersectionVoxel = Vector3DInt32(0,0,0);
m_result.previousVoxel = Vector3DInt32(0,0,0);
return RaycastResults::Completed;
}
template<typename VolumeType, typename Callback>
RaycastResult raycastWithDirection(VolumeType* volData, const Vector3DFloat& v3dStart, const Vector3DFloat& v3dDirectionAndLength, Callback& callback)
{
Vector3DFloat v3dEnd = v3dStart + v3dDirectionAndLength;
return raycastWithEndpoints<VolumeType, Callback>(volData, v3dStart, v3dEnd, callback);
}
}

View File

@ -1,62 +0,0 @@
/*******************************************************************************
Copyright (c) 2005-2009 David 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.
*******************************************************************************/
#ifndef __PolyVox_RaycastWithCallback_H__
#define __PolyVox_RaycastWithCallback_H__
#include "PolyVoxCore/Vector.h"
namespace PolyVox
{
template<typename VolumeType>
class RaycastWithCallback
{
public:
///Constructor
RaycastWithCallback(VolumeType* volData, const Vector3DFloat& v3dStart, const Vector3DFloat& v3dDirectionAndLength, polyvox_function<bool(const Vector3DInt32& position)> funcCallback);
///Sets the start position for the ray.
void setStart(const Vector3DFloat& v3dStart);
///Set the direction for the ray.
void setDirection(const Vector3DFloat& v3dDirectionAndLength);
///Performs the raycast.
void execute();
private:
polyvox_function<bool(const Vector3DInt32& position)> m_funcCallback;
void doRaycast(float x1, float y1, float z1, float x2, float y2, float z2);
VolumeType* m_volData;
typename VolumeType::Sampler m_sampVolume;
Vector3DFloat m_v3dStart;
Vector3DFloat m_v3dDirectionAndLength;
float m_fMaxDistance;
};
}
#include "PolyVoxCore/RaycastWithCallback.inl"
#endif //__PolyVox_RaycastWithCallback_H__

View File

@ -1,155 +0,0 @@
/*******************************************************************************
Copyright (c) 2005-2009 David 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.
*******************************************************************************/
namespace PolyVox
{
template<typename VolumeType>
RaycastWithCallback<VolumeType>::RaycastWithCallback(VolumeType* volData, const Vector3DFloat& v3dStart, const Vector3DFloat& v3dDirectionAndLength, polyvox_function<bool(const Vector3DInt32& position)> funcCallback)
:m_volData(volData)
,m_sampVolume(volData)
,m_v3dStart(v3dStart)
,m_v3dDirectionAndLength(v3dDirectionAndLength)
,m_funcCallback(funcCallback)
{
//Check the user provided a callback, because it
//is used to determine when to finish the raycast.
assert(m_funcCallback);
}
template<typename VolumeType>
void RaycastWithCallback<VolumeType>::setStart(const Vector3DFloat& v3dStart)
{
m_v3dStart = v3dStart;
}
template<typename VolumeType>
void RaycastWithCallback<VolumeType>::setDirection(const Vector3DFloat& v3dDirectionAndLength)
{
m_v3dDirectionAndLength = v3dDirectionAndLength;
}
template<typename VolumeType>
void RaycastWithCallback<VolumeType>::execute(void)
{
//The doRaycast function is assuming that it is iterating over the areas defined between
//voxels. We actually want to define the areas as being centered on voxels (as this is
//what the CubicSurfaceExtractor generates). We add (0.5,0.5,0.5) here to adjust for this.
Vector3DFloat v3dStart = m_v3dStart + Vector3DFloat(0.5f, 0.5f, 0.5f);
//Compute the end point
Vector3DFloat v3dEnd = v3dStart + m_v3dDirectionAndLength;
//Do the raycast
doRaycast(v3dStart.getX(), v3dStart.getY(), v3dStart.getZ(), v3dEnd.getX(), v3dEnd.getY(), v3dEnd.getZ());
}
// This function is based on Christer Ericson's code and description of the 'Uniform Grid Intersection Test' in
// 'Real Time Collision Detection'. The following information from the errata on the book website is also relevent:
//
// pages 326-327. In the function VisitCellsOverlapped() the two lines calculating tx and ty are incorrect.
// The less-than sign in each line should be a greater-than sign. That is, the two lines should read:
//
// float tx = ((x1 > x2) ? (x1 - minx) : (maxx - x1)) / Abs(x2 - x1);
// float ty = ((y1 > y2) ? (y1 - miny) : (maxy - y1)) / Abs(y2 - y1);
//
// Thanks to Jetro Lauha of Fathammer in Helsinki, Finland for reporting this error.
//
// Jetro also points out that the computations of i, j, iend, and jend are incorrectly rounded if the line
// coordinates are allowed to go negative. While that was not really the intent of the code — that is, I
// assumed grids to be numbered from (0, 0) to (m, n) — I'm at fault for not making my assumption clear.
// Where it is important to handle negative line coordinates the computation of these variables should be
// changed to something like this:
//
// // Determine start grid cell coordinates (i, j)
// int i = (int)floorf(x1 / CELL_SIDE);
// int j = (int)floorf(y1 / CELL_SIDE);
//
// // Determine end grid cell coordinates (iend, jend)
// int iend = (int)floorf(x2 / CELL_SIDE);
// int jend = (int)floorf(y2 / CELL_SIDE);
//
// page 328. The if-statement that reads "if (ty <= tx && ty <= tz)" has a superfluous condition.
// It should simply read "if (ty <= tz)".
//
// This error was reported by Joey Hammer (PixelActive).
template<typename VolumeType>
void RaycastWithCallback<VolumeType>::doRaycast(float x1, float y1, float z1, float x2, float y2, float z2)
{
int i = (int)floorf(x1);
int j = (int)floorf(y1);
int k = (int)floorf(z1);
int iend = (int)floorf(x2);
int jend = (int)floorf(y2);
int kend = (int)floorf(z2);
int di = ((x1 < x2) ? 1 : ((x1 > x2) ? -1 : 0));
int dj = ((y1 < y2) ? 1 : ((y1 > y2) ? -1 : 0));
int dk = ((z1 < z2) ? 1 : ((z1 > z2) ? -1 : 0));
float deltatx = 1.0f / std::abs(x2 - x1);
float deltaty = 1.0f / std::abs(y2 - y1);
float deltatz = 1.0f / std::abs(z2 - z1);
float minx = floorf(x1), maxx = minx + 1.0f;
float tx = ((x1 > x2) ? (x1 - minx) : (maxx - x1)) * deltatx;
float miny = floorf(y1), maxy = miny + 1.0f;
float ty = ((y1 > y2) ? (y1 - miny) : (maxy - y1)) * deltaty;
float minz = floorf(z1), maxz = minz + 1.0f;
float tz = ((z1 > z2) ? (z1 - minz) : (maxz - z1)) * deltatz;
m_sampVolume.setPosition(i,j,k);
for(;;)
{
//Call the callback. If it returns false then finish the loop.
if(!m_funcCallback(Vector3DInt32(i,j,k)))
{
break;
}
if(tx <= ty && tx <= tz)
{
tx += deltatx;
i += di;
if(di == 1) m_sampVolume.movePositiveX();
if(di == -1) m_sampVolume.moveNegativeX();
} else if (ty <= tz)
{
ty += deltaty;
j += dj;
if(dj == 1) m_sampVolume.movePositiveY();
if(dj == -1) m_sampVolume.moveNegativeY();
} else
{
tz += deltatz;
k += dk;
if(dk == 1) m_sampVolume.movePositiveZ();
if(dk == -1) m_sampVolume.moveNegativeZ();
}
}
}
}

View File

@ -30,10 +30,14 @@ freely, subject to the following restrictions:
using namespace PolyVox;
bool isVoxelTransparent(uint8_t voxel)
class IsVoxelTransparent
{
return voxel == 0;
}
public:
bool operator()(uint8_t voxel)
{
return voxel == 0;
}
};
void TestAmbientOcclusionGenerator::testExecute()
{
@ -61,11 +65,9 @@ void TestAmbientOcclusionGenerator::testExecute()
const int32_t g_uArraySideLength = g_uVolumeSideLength / 2;
Array<3, uint8_t> ambientOcclusionResult(ArraySizes(g_uArraySideLength)(g_uArraySideLength)(g_uArraySideLength));
//Create the ambient occlusion calculator
AmbientOcclusionCalculator< SimpleVolume<uint8_t> > calculator(&volData, &ambientOcclusionResult, volData.getEnclosingRegion(), 32.0f, 255, isVoxelTransparent);
//Execute the calculator
calculator.execute();
// Calculate the ambient occlusion values
IsVoxelTransparent isVoxelTransparent;
calculateAmbientOcclusion(&volData, &ambientOcclusionResult, volData.getEnclosingRegion(), 32.0f, 255, isVoxelTransparent);
//Check the results by sampling along a line though the centre of the volume. Because
//of the two walls we added, samples in the middle are darker than those at the edge.

View File

@ -33,10 +33,28 @@ freely, subject to the following restrictions:
using namespace PolyVox;
bool isPassableByRay(const SimpleVolume<int8_t>::Sampler& sampler)
// This is the callback functor which is called by the raycast() function for every voxel it touches.
// It's primary purpose is to tell the raycast whether or not to continue (i.e. it tests whether the
// ray has hit a solid voxel). Because the instance of this class is passed to the raycast() function
// by reference we can also use it to encapsulate some state. We're testing this by counting the total
// number of voxels touched.
class RaycastTestFunctor
{
return sampler.getVoxel() <= 0;
}
public:
RaycastTestFunctor()
:m_uTotalVoxelsTouched(0)
{
}
bool operator()(const SimpleVolume<int8_t>::Sampler& sampler)
{
m_uTotalVoxelsTouched++;
return sampler.getVoxel() <= 0;
}
uint32_t m_uTotalVoxelsTouched;
};
void TestRaycast::testExecute()
{
@ -64,20 +82,32 @@ void TestRaycast::testExecute()
//Cast rays from the centre. Roughly 2/3 should escape.
Vector3DFloat start (uVolumeSideLength / 2, uVolumeSideLength / 2, uVolumeSideLength / 2);
// For demonstration purposes we are using the same function object for all raycasts.
// Therefore, the state it maintains (total voxels touched) is accumulated over all raycsts.
RaycastTestFunctor raycastTestFunctor;
// We could have counted the total number of hits in the same way as the total number of voxels
// touched, but for demonstration and testing purposes we are making use of the raycast return value
// and counting them seperatly in this variable.
int hits = 0;
// Cast a large number of random rays
for(int ct = 0; ct < 1000000; ct++)
{
RaycastResult result;
Raycast< SimpleVolume<int8_t> > raycast(&volData, start, randomUnitVectors[ct % 1024] * 1000.0f, result, isPassableByRay);
raycast.execute();
if(result.foundIntersection)
RaycastResult result = raycastWithDirection(&volData, start, randomUnitVectors[ct % 1024] * 1000.0f, raycastTestFunctor);
if(result == RaycastResults::Interupted)
{
hits++;
}
}
}
//Check the number of hits.
// Check the number of hits.
QCOMPARE(hits, 687494);
// Check the total number of voxels touched
QCOMPARE(raycastTestFunctor.m_uTotalVoxelsTouched, static_cast<uint32_t>(486219343));
}
QTEST_MAIN(TestRaycast)