Work on gradient estimators. Added CENTRAL_DIFFERENCE_SMOOTHED and SOBEL_SMOOTHED.

This commit is contained in:
David Williams 2009-03-09 23:21:03 +00:00
parent 34c41cd32e
commit 31c0b24a27
4 changed files with 197 additions and 122 deletions

View File

@ -26,9 +26,11 @@ namespace PolyVox
{ {
enum NormalGenerationMethod enum NormalGenerationMethod
{ {
SIMPLE, SIMPLE, //Fastest
CENTRAL_DIFFERENCE, CENTRAL_DIFFERENCE,
SOBEL SOBEL,
CENTRAL_DIFFERENCE_SMOOTHED,
SOBEL_SMOOTHED //Smoothest
}; };
} }

View File

@ -39,9 +39,11 @@ namespace PolyVox
template <typename VoxelType> template <typename VoxelType>
Vector3DFloat computeSobelGradient(const BlockVolumeIterator<VoxelType>& volIter); Vector3DFloat computeSobelGradient(const BlockVolumeIterator<VoxelType>& volIter);
template <typename VoxelType>
Vector3DFloat computeSmoothSobelGradient(BlockVolumeIterator<VoxelType>& volIter);
POLYVOX_API void computeNormalsForVertices(BlockVolume<uint8>* volumeData, IndexedSurfacePatch& isp, NormalGenerationMethod normalGenerationMethod); POLYVOX_API void computeNormalsForVertices(BlockVolume<uint8>* volumeData, IndexedSurfacePatch& isp, NormalGenerationMethod normalGenerationMethod);
POLYVOX_API Vector3DFloat computeNormal(BlockVolume<uint8>* volumeData, const Vector3DFloat& position, NormalGenerationMethod normalGenerationMethod); POLYVOX_API Vector3DFloat computeNormal(BlockVolume<uint8>* volumeData, const Vector3DFloat& v3dPos, NormalGenerationMethod normalGenerationMethod);
} }
#include "GradientEstimators.inl" #include "GradientEstimators.inl"

View File

@ -182,4 +182,121 @@ namespace PolyVox
//For our normals we want the the other way around, so we switch the components as we return them. //For our normals we want the the other way around, so we switch the components as we return them.
return Vector3DFloat(static_cast<float>(-xGrad),static_cast<float>(-yGrad),static_cast<float>(-zGrad)); return Vector3DFloat(static_cast<float>(-xGrad),static_cast<float>(-yGrad),static_cast<float>(-zGrad));
} }
template <typename VoxelType>
Vector3DFloat computeSmoothSobelGradient(BlockVolumeIterator<VoxelType>& volIter)
{
static const int weights[3][3][3] = { { {2,3,2}, {3,6,3}, {2,3,2} }, {
{3,6,3}, {6,0,6}, {3,6,3} }, { {2,3,2}, {3,6,3}, {2,3,2} } };
uint16 initialX = volIter.getPosX();
uint16 initialY = volIter.getPosY();
uint16 initialZ = volIter.getPosZ();
volIter.setPosition(initialX-1, initialY-1, initialZ-1); const float pVoxel1nx1ny1nz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX-1, initialY-1, initialZ ); const float pVoxel1nx1ny0pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX-1, initialY-1, initialZ+1); const float pVoxel1nx1ny1pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX-1, initialY , initialZ-1); const float pVoxel1nx0py1nz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX-1, initialY , initialZ ); const float pVoxel1nx0py0pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX-1, initialY , initialZ+1); const float pVoxel1nx0py1pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX-1, initialY+1, initialZ-1); const float pVoxel1nx1py1nz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX-1, initialY+1, initialZ ); const float pVoxel1nx1py0pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX-1, initialY+1, initialZ+1); const float pVoxel1nx1py1pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX , initialY-1, initialZ-1); const float pVoxel0px1ny1nz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX , initialY-1, initialZ ); const float pVoxel0px1ny0pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX , initialY-1, initialZ+1); const float pVoxel0px1ny1pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX , initialY , initialZ-1); const float pVoxel0px0py1nz = computeSmoothedVoxel(volIter);
//volIter.setPosition(initialX , initialY , initialZ ); const float pVoxel0px0py0pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX , initialY , initialZ+1); const float pVoxel0px0py1pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX , initialY+1, initialZ-1); const float pVoxel0px1py1nz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX , initialY+1, initialZ ); const float pVoxel0px1py0pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX , initialY+1, initialZ+1); const float pVoxel0px1py1pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX+1, initialY-1, initialZ-1); const float pVoxel1px1ny1nz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX+1, initialY-1, initialZ ); const float pVoxel1px1ny0pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX+1, initialY-1, initialZ+1); const float pVoxel1px1ny1pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX+1, initialY , initialZ-1); const float pVoxel1px0py1nz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX+1, initialY , initialZ ); const float pVoxel1px0py0pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX+1, initialY , initialZ+1); const float pVoxel1px0py1pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX+1, initialY+1, initialZ-1); const float pVoxel1px1py1nz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX+1, initialY+1, initialZ ); const float pVoxel1px1py0pz = computeSmoothedVoxel(volIter);
volIter.setPosition(initialX+1, initialY+1, initialZ+1); const float pVoxel1px1py1pz = computeSmoothedVoxel(volIter);
/*const VoxelType pVoxel1nx1ny1nz = volIter.peekVoxel1nx1ny1nz() > 0 ? 1: 0;
const VoxelType pVoxel1nx1ny0pz = volIter.peekVoxel1nx1ny0pz() > 0 ? 1: 0;
const VoxelType pVoxel1nx1ny1pz = volIter.peekVoxel1nx1ny1pz() > 0 ? 1: 0;
const VoxelType pVoxel1nx0py1nz = volIter.peekVoxel1nx0py1nz() > 0 ? 1: 0;
const VoxelType pVoxel1nx0py0pz = volIter.peekVoxel1nx0py0pz() > 0 ? 1: 0;
const VoxelType pVoxel1nx0py1pz = volIter.peekVoxel1nx0py1pz() > 0 ? 1: 0;
const VoxelType pVoxel1nx1py1nz = volIter.peekVoxel1nx1py1nz() > 0 ? 1: 0;
const VoxelType pVoxel1nx1py0pz = volIter.peekVoxel1nx1py0pz() > 0 ? 1: 0;
const VoxelType pVoxel1nx1py1pz = volIter.peekVoxel1nx1py1pz() > 0 ? 1: 0;
const VoxelType pVoxel0px1ny1nz = volIter.peekVoxel0px1ny1nz() > 0 ? 1: 0;
const VoxelType pVoxel0px1ny0pz = volIter.peekVoxel0px1ny0pz() > 0 ? 1: 0;
const VoxelType pVoxel0px1ny1pz = volIter.peekVoxel0px1ny1pz() > 0 ? 1: 0;
const VoxelType pVoxel0px0py1nz = volIter.peekVoxel0px0py1nz() > 0 ? 1: 0;
//const VoxelType pVoxel0px0py0pz = volIter.peekVoxel0px0py0pz() > 0 ? 1: 0;
const VoxelType pVoxel0px0py1pz = volIter.peekVoxel0px0py1pz() > 0 ? 1: 0;
const VoxelType pVoxel0px1py1nz = volIter.peekVoxel0px1py1nz() > 0 ? 1: 0;
const VoxelType pVoxel0px1py0pz = volIter.peekVoxel0px1py0pz() > 0 ? 1: 0;
const VoxelType pVoxel0px1py1pz = volIter.peekVoxel0px1py1pz() > 0 ? 1: 0;
const VoxelType pVoxel1px1ny1nz = volIter.peekVoxel1px1ny1nz() > 0 ? 1: 0;
const VoxelType pVoxel1px1ny0pz = volIter.peekVoxel1px1ny0pz() > 0 ? 1: 0;
const VoxelType pVoxel1px1ny1pz = volIter.peekVoxel1px1ny1pz() > 0 ? 1: 0;
const VoxelType pVoxel1px0py1nz = volIter.peekVoxel1px0py1nz() > 0 ? 1: 0;
const VoxelType pVoxel1px0py0pz = volIter.peekVoxel1px0py0pz() > 0 ? 1: 0;
const VoxelType pVoxel1px0py1pz = volIter.peekVoxel1px0py1pz() > 0 ? 1: 0;
const VoxelType pVoxel1px1py1nz = volIter.peekVoxel1px1py1nz() > 0 ? 1: 0;
const VoxelType pVoxel1px1py0pz = volIter.peekVoxel1px1py0pz() > 0 ? 1: 0;
const VoxelType pVoxel1px1py1pz = volIter.peekVoxel1px1py1pz() > 0 ? 1: 0;*/
const float xGrad(- weights[0][0][0] * pVoxel1nx1ny1nz -
weights[1][0][0] * pVoxel1nx1ny0pz - weights[2][0][0] *
pVoxel1nx1ny1pz - weights[0][1][0] * pVoxel1nx0py1nz -
weights[1][1][0] * pVoxel1nx0py0pz - weights[2][1][0] *
pVoxel1nx0py1pz - weights[0][2][0] * pVoxel1nx1py1nz -
weights[1][2][0] * pVoxel1nx1py0pz - weights[2][2][0] *
pVoxel1nx1py1pz + weights[0][0][2] * pVoxel1px1ny1nz +
weights[1][0][2] * pVoxel1px1ny0pz + weights[2][0][2] *
pVoxel1px1ny1pz + weights[0][1][2] * pVoxel1px0py1nz +
weights[1][1][2] * pVoxel1px0py0pz + weights[2][1][2] *
pVoxel1px0py1pz + weights[0][2][2] * pVoxel1px1py1nz +
weights[1][2][2] * pVoxel1px1py0pz + weights[2][2][2] *
pVoxel1px1py1pz);
const float yGrad(- weights[0][0][0] * pVoxel1nx1ny1nz -
weights[1][0][0] * pVoxel1nx1ny0pz - weights[2][0][0] *
pVoxel1nx1ny1pz + weights[0][2][0] * pVoxel1nx1py1nz +
weights[1][2][0] * pVoxel1nx1py0pz + weights[2][2][0] *
pVoxel1nx1py1pz - weights[0][0][1] * pVoxel0px1ny1nz -
weights[1][0][1] * pVoxel0px1ny0pz - weights[2][0][1] *
pVoxel0px1ny1pz + weights[0][2][1] * pVoxel0px1py1nz +
weights[1][2][1] * pVoxel0px1py0pz + weights[2][2][1] *
pVoxel0px1py1pz - weights[0][0][2] * pVoxel1px1ny1nz -
weights[1][0][2] * pVoxel1px1ny0pz - weights[2][0][2] *
pVoxel1px1ny1pz + weights[0][2][2] * pVoxel1px1py1nz +
weights[1][2][2] * pVoxel1px1py0pz + weights[2][2][2] *
pVoxel1px1py1pz);
const float zGrad(- weights[0][0][0] * pVoxel1nx1ny1nz +
weights[2][0][0] * pVoxel1nx1ny1pz - weights[0][1][0] *
pVoxel1nx0py1nz + weights[2][1][0] * pVoxel1nx0py1pz -
weights[0][2][0] * pVoxel1nx1py1nz + weights[2][2][0] *
pVoxel1nx1py1pz - weights[0][0][1] * pVoxel0px1ny1nz +
weights[2][0][1] * pVoxel0px1ny1pz - weights[0][1][1] *
pVoxel0px0py1nz + weights[2][1][1] * pVoxel0px0py1pz -
weights[0][2][1] * pVoxel0px1py1nz + weights[2][2][1] *
pVoxel0px1py1pz - weights[0][0][2] * pVoxel1px1ny1nz +
weights[2][0][2] * pVoxel1px1ny1pz - weights[0][1][2] *
pVoxel1px0py1nz + weights[2][1][2] * pVoxel1px0py1pz -
weights[0][2][2] * pVoxel1px1py1nz + weights[2][2][2] *
pVoxel1px1py1pz);
//Note: The above actually give gradients going from low density to high density.
//For our normals we want the the other way around, so we switch the components as we return them.
return Vector3DFloat(-xGrad,-yGrad,-zGrad);
}
} }

View File

@ -20,51 +20,13 @@ namespace PolyVox
BlockVolumeIterator<uint8> volIter(*volumeData); BlockVolumeIterator<uint8> volIter(*volumeData);
//Check all corners are within the volume, allowing a boundary for gradient estimation //Check all corners are within the volume, allowing a boundary for gradient estimation
bool lowerCornerInside = volumeData->containsPoint(v3dFloor,1); bool lowerCornerInside = volumeData->containsPoint(v3dFloor,2);
bool upperCornerInside = volumeData->containsPoint(v3dFloor+Vector3DInt32(1,1,1),1); bool upperCornerInside = volumeData->containsPoint(v3dFloor+Vector3DInt32(1,1,1),2);
if(lowerCornerInside && upperCornerInside) //If this test fails the vertex will be left as it was if(lowerCornerInside && upperCornerInside) //If this test fails the vertex will be left as it was
{ {
Vector3DFloat v3dGradient; //To store the result Vector3DFloat v3dGradient = computeNormal(volumeData, v3dPos, normalGenerationMethod);
if(normalGenerationMethod == SOBEL)
{
volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor));
const Vector3DFloat gradFloor = computeSobelGradient(volIter);
if((v3dPos.getX() - v3dFloor.getX()) > 0.001)
{
volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor+Vector3DInt32(1,0,0)));
}
if((v3dPos.getY() - v3dFloor.getY()) > 0.001)
{
volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor+Vector3DInt32(0,1,0)));
}
if((v3dPos.getZ() - v3dFloor.getZ()) > 0.001)
{
volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor+Vector3DInt32(0,0,1)));
}
const Vector3DFloat gradCeil = computeSobelGradient(volIter);
v3dGradient = (gradFloor + gradCeil);
}
if(normalGenerationMethod == CENTRAL_DIFFERENCE)
{
volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor));
const Vector3DFloat gradFloor = computeCentralDifferenceGradient(volIter);
if((v3dPos.getX() - v3dFloor.getX()) > 0.001)
{
volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor+Vector3DInt32(1,0,0)));
}
if((v3dPos.getY() - v3dFloor.getY()) > 0.001)
{
volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor+Vector3DInt32(0,1,0)));
}
if((v3dPos.getZ() - v3dFloor.getZ()) > 0.001)
{
volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor+Vector3DInt32(0,0,1)));
}
const Vector3DFloat gradCeil = computeCentralDifferenceGradient(volIter);
v3dGradient = (gradFloor + gradCeil);
}
if(v3dGradient.lengthSquared() > 0.0001) if(v3dGradient.lengthSquared() > 0.0001)
{ {
//If we got a normal of significant length then update it. //If we got a normal of significant length then update it.
@ -77,97 +39,89 @@ namespace PolyVox
} }
} }
Vector3DFloat computeNormal(BlockVolume<uint8>* volumeData, const Vector3DFloat& position, NormalGenerationMethod normalGenerationMethod) Vector3DFloat computeNormal(BlockVolume<uint8>* volumeData, const Vector3DFloat& v3dPos, NormalGenerationMethod normalGenerationMethod)
{ {
const float posX = position.getX(); Vector3DFloat v3dGradient; //To store the result
const float posY = position.getY();
const float posZ = position.getZ();
const uint16 floorX = static_cast<uint16>(posX); BlockVolumeIterator<uint8> volIter(*volumeData);
const uint16 floorY = static_cast<uint16>(posY);
const uint16 floorZ = static_cast<uint16>(posZ);
//Check all corners are within the volume, allowing a boundary for gradient estimation const Vector3DInt32 v3dFloor = static_cast<Vector3DInt32>(v3dPos);
bool lowerCornerInside = volumeData->containsPoint(Vector3DInt32(floorX, floorY, floorZ),1);
bool upperCornerInside = volumeData->containsPoint(Vector3DInt32(floorX+1, floorY+1, floorZ+1),1);
if((!lowerCornerInside) || (!upperCornerInside))
{
normalGenerationMethod = SIMPLE;
}
Vector3DFloat result; volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor));
Vector3DFloat gradFloor;
BlockVolumeIterator<uint8> volIter(*volumeData); //FIXME - save this somewhere - could be expensive to create? switch(normalGenerationMethod)
{
case SOBEL_SMOOTHED:
if(normalGenerationMethod == SOBEL) gradFloor = computeSmoothSobelGradient(volIter);
{ break;
volIter.setPosition(static_cast<uint16>(posX),static_cast<uint16>(posY),static_cast<uint16>(posZ)); case CENTRAL_DIFFERENCE_SMOOTHED:
const Vector3DFloat gradFloor = computeSobelGradient(volIter); gradFloor = computeSmoothCentralDifferenceGradient(volIter);
if((posX - floorX) > 0.25) //The result should be 0.0 or 0.5 break;
{ case SOBEL:
volIter.setPosition(static_cast<uint16>(posX+1.0),static_cast<uint16>(posY),static_cast<uint16>(posZ)); gradFloor = computeSobelGradient(volIter);
break;
case CENTRAL_DIFFERENCE:
gradFloor = computeCentralDifferenceGradient(volIter);
break;
} }
if((posY - floorY) > 0.25) //The result should be 0.0 or 0.5
if((v3dPos.getX() - v3dFloor.getX()) > 0.25) //The result should be 0.0 or 0.5
{ {
volIter.setPosition(static_cast<uint16>(posX),static_cast<uint16>(posY+1.0),static_cast<uint16>(posZ)); volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor+Vector3DInt32(1,0,0)));
} }
if((posZ - floorZ) > 0.25) //The result should be 0.0 or 0.5 if((v3dPos.getY() - v3dFloor.getY()) > 0.25) //The result should be 0.0 or 0.5
{ {
volIter.setPosition(static_cast<uint16>(posX),static_cast<uint16>(posY),static_cast<uint16>(posZ+1.0)); volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor+Vector3DInt32(0,1,0)));
} }
const Vector3DFloat gradCeil = computeSobelGradient(volIter); if((v3dPos.getZ() - v3dFloor.getZ()) > 0.25) //The result should be 0.0 or 0.5
result = ((gradFloor + gradCeil) * -1.0f); {
if(result.lengthSquared() < 0.0001) volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor+Vector3DInt32(0,0,1)));
}
Vector3DFloat gradCeil;
switch(normalGenerationMethod)
{
case SOBEL_SMOOTHED:
gradCeil = computeSmoothSobelGradient(volIter);
break;
case CENTRAL_DIFFERENCE_SMOOTHED:
gradCeil = computeSmoothCentralDifferenceGradient(volIter);
break;
case SOBEL:
gradCeil = computeSobelGradient(volIter);
break;
case CENTRAL_DIFFERENCE:
gradCeil = computeCentralDifferenceGradient(volIter);
break;
}
v3dGradient = (gradFloor + gradCeil);
if(v3dGradient.lengthSquared() < 0.0001)
{ {
//Operation failed - fall back on simple gradient estimation //Operation failed - fall back on simple gradient estimation
normalGenerationMethod = SIMPLE; normalGenerationMethod = SIMPLE;
} }
}
if(normalGenerationMethod == CENTRAL_DIFFERENCE) if(normalGenerationMethod == SIMPLE)
{
volIter.setPosition(static_cast<uint16>(posX),static_cast<uint16>(posY),static_cast<uint16>(posZ));
const Vector3DFloat gradFloor = computeCentralDifferenceGradient(volIter);
if((posX - floorX) > 0.25) //The result should be 0.0 or 0.5
{
volIter.setPosition(static_cast<uint16>(posX+1.0),static_cast<uint16>(posY),static_cast<uint16>(posZ));
}
if((posY - floorY) > 0.25) //The result should be 0.0 or 0.5
{
volIter.setPosition(static_cast<uint16>(posX),static_cast<uint16>(posY+1.0),static_cast<uint16>(posZ));
}
if((posZ - floorZ) > 0.25) //The result should be 0.0 or 0.5
{
volIter.setPosition(static_cast<uint16>(posX),static_cast<uint16>(posY),static_cast<uint16>(posZ+1.0));
}
const Vector3DFloat gradCeil = computeCentralDifferenceGradient(volIter);
result = ((gradFloor + gradCeil) * -1.0f);
if(result.lengthSquared() < 0.0001)
{ {
//Operation failed - fall back on simple gradient estimation volIter.setPosition(static_cast<Vector3DInt16>(v3dFloor));
normalGenerationMethod = SIMPLE; const uint8 uFloor = volIter.getVoxel() > 0 ? 1 : 0;
if((v3dPos.getX() - v3dFloor.getX()) > 0.25) //The result should be 0.0 or 0.5
{
uint8 uCeil = volIter.peekVoxel1px0py0pz() > 0 ? 1 : 0;
v3dGradient = Vector3DFloat(static_cast<float>(uFloor - uCeil),0.0,0.0);
}
else if((v3dPos.getY() - v3dFloor.getY()) > 0.25) //The result should be 0.0 or 0.5
{
uint8 uCeil = volIter.peekVoxel0px1py0pz() > 0 ? 1 : 0;
v3dGradient = Vector3DFloat(0.0,static_cast<float>(uFloor - uCeil),0.0);
}
else if((v3dPos.getZ() - v3dFloor.getZ()) > 0.25) //The result should be 0.0 or 0.5
{
uint8 uCeil = volIter.peekVoxel0px0py1pz() > 0 ? 1 : 0;
v3dGradient = Vector3DFloat(0.0, 0.0,static_cast<float>(uFloor - uCeil));
}
} }
} return v3dGradient;
if(normalGenerationMethod == SIMPLE)
{
volIter.setPosition(static_cast<uint16>(posX),static_cast<uint16>(posY),static_cast<uint16>(posZ));
const uint8 uFloor = volIter.getVoxel() > 0 ? 1 : 0;
if((posX - floorX) > 0.25) //The result should be 0.0 or 0.5
{
uint8 uCeil = volIter.peekVoxel1px0py0pz() > 0 ? 1 : 0;
result = Vector3DFloat(static_cast<float>(uFloor - uCeil),0.0,0.0);
}
else if((posY - floorY) > 0.25) //The result should be 0.0 or 0.5
{
uint8 uCeil = volIter.peekVoxel0px1py0pz() > 0 ? 1 : 0;
result = Vector3DFloat(0.0,static_cast<float>(uFloor - uCeil),0.0);
}
else if((posZ - floorZ) > 0.25) //The result should be 0.0 or 0.5
{
uint8 uCeil = volIter.peekVoxel0px0py1pz() > 0 ? 1 : 0;
result = Vector3DFloat(0.0, 0.0,static_cast<float>(uFloor - uCeil));
}
}
return result;
} }
} }