344 lines
14 KiB
C++
344 lines
14 KiB
C++
/*
|
||
---------------------------------------------------------------------------
|
||
Open Asset Import Library (assimp)
|
||
---------------------------------------------------------------------------
|
||
|
||
Copyright (c) 2006-2015, assimp team
|
||
|
||
All rights reserved.
|
||
|
||
Redistribution and use of this software in source and binary forms,
|
||
with or without modification, are permitted provided that the following
|
||
conditions are met:
|
||
|
||
* Redistributions of source code must retain the above
|
||
copyright notice, this list of conditions and the
|
||
following disclaimer.
|
||
|
||
* Redistributions in binary form must reproduce the above
|
||
copyright notice, this list of conditions and the
|
||
following disclaimer in the documentation and/or other
|
||
materials provided with the distribution.
|
||
|
||
* Neither the name of the assimp team, nor the names of its
|
||
contributors may be used to endorse or promote products
|
||
derived from this software without specific prior
|
||
written permission of the assimp team.
|
||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
---------------------------------------------------------------------------
|
||
*/
|
||
|
||
/** @file Implementation of the helper class to quickly find vertices close to a given position */
|
||
|
||
#include "SpatialSort.h"
|
||
#include <boost/static_assert.hpp>
|
||
#include "../include/assimp/ai_assert.h"
|
||
|
||
using namespace Assimp;
|
||
|
||
// CHAR_BIT seems to be defined under MVSC, but not under GCC. Pray that the correct value is 8.
|
||
#ifndef CHAR_BIT
|
||
# define CHAR_BIT 8
|
||
#endif
|
||
|
||
// ------------------------------------------------------------------------------------------------
|
||
// Constructs a spatially sorted representation from the given position array.
|
||
SpatialSort::SpatialSort( const aiVector3D* pPositions, unsigned int pNumPositions,
|
||
unsigned int pElementOffset)
|
||
|
||
// define the reference plane. We choose some arbitrary vector away from all basic axises
|
||
// in the hope that no model spreads all its vertices along this plane.
|
||
: mPlaneNormal(0.8523f, 0.34321f, 0.5736f)
|
||
{
|
||
mPlaneNormal.Normalize();
|
||
Fill(pPositions,pNumPositions,pElementOffset);
|
||
}
|
||
|
||
// ------------------------------------------------------------------------------------------------
|
||
SpatialSort :: SpatialSort()
|
||
: mPlaneNormal(0.8523f, 0.34321f, 0.5736f)
|
||
{
|
||
mPlaneNormal.Normalize();
|
||
}
|
||
|
||
// ------------------------------------------------------------------------------------------------
|
||
// Destructor
|
||
SpatialSort::~SpatialSort()
|
||
{
|
||
// nothing to do here, everything destructs automatically
|
||
}
|
||
|
||
// ------------------------------------------------------------------------------------------------
|
||
void SpatialSort::Fill( const aiVector3D* pPositions, unsigned int pNumPositions,
|
||
unsigned int pElementOffset,
|
||
bool pFinalize /*= true */)
|
||
{
|
||
mPositions.clear();
|
||
Append(pPositions,pNumPositions,pElementOffset,pFinalize);
|
||
}
|
||
|
||
// ------------------------------------------------------------------------------------------------
|
||
void SpatialSort :: Finalize()
|
||
{
|
||
std::sort( mPositions.begin(), mPositions.end());
|
||
}
|
||
|
||
// ------------------------------------------------------------------------------------------------
|
||
void SpatialSort::Append( const aiVector3D* pPositions, unsigned int pNumPositions,
|
||
unsigned int pElementOffset,
|
||
bool pFinalize /*= true */)
|
||
{
|
||
// store references to all given positions along with their distance to the reference plane
|
||
const size_t initial = mPositions.size();
|
||
mPositions.reserve(initial + (pFinalize?pNumPositions:pNumPositions*2));
|
||
for( unsigned int a = 0; a < pNumPositions; a++)
|
||
{
|
||
const char* tempPointer = reinterpret_cast<const char*> (pPositions);
|
||
const aiVector3D* vec = reinterpret_cast<const aiVector3D*> (tempPointer + a * pElementOffset);
|
||
|
||
// store position by index and distance
|
||
float distance = *vec * mPlaneNormal;
|
||
mPositions.push_back( Entry( a+initial, *vec, distance));
|
||
}
|
||
|
||
if (pFinalize) {
|
||
// now sort the array ascending by distance.
|
||
Finalize();
|
||
}
|
||
}
|
||
|
||
// ------------------------------------------------------------------------------------------------
|
||
// Returns an iterator for all positions close to the given position.
|
||
void SpatialSort::FindPositions( const aiVector3D& pPosition,
|
||
float pRadius, std::vector<unsigned int>& poResults) const
|
||
{
|
||
const float dist = pPosition * mPlaneNormal;
|
||
const float minDist = dist - pRadius, maxDist = dist + pRadius;
|
||
|
||
// clear the array in this strange fashion because a simple clear() would also deallocate
|
||
// the array which we want to avoid
|
||
poResults.erase( poResults.begin(), poResults.end());
|
||
|
||
// quick check for positions outside the range
|
||
if( mPositions.size() == 0)
|
||
return;
|
||
if( maxDist < mPositions.front().mDistance)
|
||
return;
|
||
if( minDist > mPositions.back().mDistance)
|
||
return;
|
||
|
||
// do a binary search for the minimal distance to start the iteration there
|
||
unsigned int index = (unsigned int)mPositions.size() / 2;
|
||
unsigned int binaryStepSize = (unsigned int)mPositions.size() / 4;
|
||
while( binaryStepSize > 1)
|
||
{
|
||
if( mPositions[index].mDistance < minDist)
|
||
index += binaryStepSize;
|
||
else
|
||
index -= binaryStepSize;
|
||
|
||
binaryStepSize /= 2;
|
||
}
|
||
|
||
// depending on the direction of the last step we need to single step a bit back or forth
|
||
// to find the actual beginning element of the range
|
||
while( index > 0 && mPositions[index].mDistance > minDist)
|
||
index--;
|
||
while( index < (mPositions.size() - 1) && mPositions[index].mDistance < minDist)
|
||
index++;
|
||
|
||
// Mow start iterating from there until the first position lays outside of the distance range.
|
||
// Add all positions inside the distance range within the given radius to the result aray
|
||
std::vector<Entry>::const_iterator it = mPositions.begin() + index;
|
||
const float pSquared = pRadius*pRadius;
|
||
while( it->mDistance < maxDist)
|
||
{
|
||
if( (it->mPosition - pPosition).SquareLength() < pSquared)
|
||
poResults.push_back( it->mIndex);
|
||
++it;
|
||
if( it == mPositions.end())
|
||
break;
|
||
}
|
||
|
||
// that's it
|
||
}
|
||
|
||
namespace {
|
||
|
||
// Binary, signed-integer representation of a single-precision floating-point value.
|
||
// IEEE 754 says: "If two floating-point numbers in the same format are ordered then they are
|
||
// ordered the same way when their bits are reinterpreted as sign-magnitude integers."
|
||
// This allows us to convert all floating-point numbers to signed integers of arbitrary size
|
||
// and then use them to work with ULPs (Units in the Last Place, for high-precision
|
||
// computations) or to compare them (integer comparisons are faster than floating-point
|
||
// comparisons on many platforms).
|
||
typedef signed int BinFloat;
|
||
|
||
// --------------------------------------------------------------------------------------------
|
||
// Converts the bit pattern of a floating-point number to its signed integer representation.
|
||
BinFloat ToBinary( const float & pValue) {
|
||
|
||
// If this assertion fails, signed int is not big enough to store a float on your platform.
|
||
// Please correct the declaration of BinFloat a few lines above - but do it in a portable,
|
||
// #ifdef'd manner!
|
||
BOOST_STATIC_ASSERT( sizeof(BinFloat) >= sizeof(float));
|
||
|
||
#if defined( _MSC_VER)
|
||
// If this assertion fails, Visual C++ has finally moved to ILP64. This means that this
|
||
// code has just become legacy code! Find out the current value of _MSC_VER and modify
|
||
// the #if above so it evaluates false on the current and all upcoming VC versions (or
|
||
// on the current platform, if LP64 or LLP64 are still used on other platforms).
|
||
BOOST_STATIC_ASSERT( sizeof(BinFloat) == sizeof(float));
|
||
|
||
// This works best on Visual C++, but other compilers have their problems with it.
|
||
const BinFloat binValue = reinterpret_cast<BinFloat const &>(pValue);
|
||
#else
|
||
// On many compilers, reinterpreting a float address as an integer causes aliasing
|
||
// problems. This is an ugly but more or less safe way of doing it.
|
||
union {
|
||
float asFloat;
|
||
BinFloat asBin;
|
||
} conversion;
|
||
conversion.asBin = 0; // zero empty space in case sizeof(BinFloat) > sizeof(float)
|
||
conversion.asFloat = pValue;
|
||
const BinFloat binValue = conversion.asBin;
|
||
#endif
|
||
|
||
// floating-point numbers are of sign-magnitude format, so find out what signed number
|
||
// representation we must convert negative values to.
|
||
// See http://en.wikipedia.org/wiki/Signed_number_representations.
|
||
|
||
// Two's complement?
|
||
if( (-42 == (~42 + 1)) && (binValue & 0x80000000))
|
||
return BinFloat(1 << (CHAR_BIT * sizeof(BinFloat) - 1)) - binValue;
|
||
// One's complement?
|
||
else if( (-42 == ~42) && (binValue & 0x80000000))
|
||
return BinFloat(-0) - binValue;
|
||
// Sign-magnitude?
|
||
else if( (-42 == (42 | (-0))) && (binValue & 0x80000000)) // -0 = 1000... binary
|
||
return binValue;
|
||
else
|
||
return binValue;
|
||
}
|
||
|
||
} // namespace
|
||
|
||
// ------------------------------------------------------------------------------------------------
|
||
// Fills an array with indices of all positions indentical to the given position. In opposite to
|
||
// FindPositions(), not an epsilon is used but a (very low) tolerance of four floating-point units.
|
||
void SpatialSort::FindIdenticalPositions( const aiVector3D& pPosition,
|
||
std::vector<unsigned int>& poResults) const
|
||
{
|
||
// Epsilons have a huge disadvantage: they are of constant precision, while floating-point
|
||
// values are of log2 precision. If you apply e=0.01 to 100, the epsilon is rather small, but
|
||
// if you apply it to 0.001, it is enormous.
|
||
|
||
// The best way to overcome this is the unit in the last place (ULP). A precision of 2 ULPs
|
||
// tells us that a float does not differ more than 2 bits from the "real" value. ULPs are of
|
||
// logarithmic precision - around 1, they are 1<>(2^24) and around 10000, they are 0.00125.
|
||
|
||
// For standard C math, we can assume a precision of 0.5 ULPs according to IEEE 754. The
|
||
// incoming vertex positions might have already been transformed, probably using rather
|
||
// inaccurate SSE instructions, so we assume a tolerance of 4 ULPs to safely identify
|
||
// identical vertex positions.
|
||
static const int toleranceInULPs = 4;
|
||
// An interesting point is that the inaccuracy grows linear with the number of operations:
|
||
// multiplying to numbers, each inaccurate to four ULPs, results in an inaccuracy of four ULPs
|
||
// plus 0.5 ULPs for the multiplication.
|
||
// To compute the distance to the plane, a dot product is needed - that is a multiplication and
|
||
// an addition on each number.
|
||
static const int distanceToleranceInULPs = toleranceInULPs + 1;
|
||
// The squared distance between two 3D vectors is computed the same way, but with an additional
|
||
// subtraction.
|
||
static const int distance3DToleranceInULPs = distanceToleranceInULPs + 1;
|
||
|
||
// Convert the plane distance to its signed integer representation so the ULPs tolerance can be
|
||
// applied. For some reason, VC won't optimize two calls of the bit pattern conversion.
|
||
const BinFloat minDistBinary = ToBinary( pPosition * mPlaneNormal) - distanceToleranceInULPs;
|
||
const BinFloat maxDistBinary = minDistBinary + 2 * distanceToleranceInULPs;
|
||
|
||
// clear the array in this strange fashion because a simple clear() would also deallocate
|
||
// the array which we want to avoid
|
||
poResults.erase( poResults.begin(), poResults.end());
|
||
|
||
// do a binary search for the minimal distance to start the iteration there
|
||
unsigned int index = (unsigned int)mPositions.size() / 2;
|
||
unsigned int binaryStepSize = (unsigned int)mPositions.size() / 4;
|
||
while( binaryStepSize > 1)
|
||
{
|
||
// Ugly, but conditional jumps are faster with integers than with floats
|
||
if( minDistBinary > ToBinary(mPositions[index].mDistance))
|
||
index += binaryStepSize;
|
||
else
|
||
index -= binaryStepSize;
|
||
|
||
binaryStepSize /= 2;
|
||
}
|
||
|
||
// depending on the direction of the last step we need to single step a bit back or forth
|
||
// to find the actual beginning element of the range
|
||
while( index > 0 && minDistBinary < ToBinary(mPositions[index].mDistance) )
|
||
index--;
|
||
while( index < (mPositions.size() - 1) && minDistBinary > ToBinary(mPositions[index].mDistance))
|
||
index++;
|
||
|
||
// Now start iterating from there until the first position lays outside of the distance range.
|
||
// Add all positions inside the distance range within the tolerance to the result aray
|
||
std::vector<Entry>::const_iterator it = mPositions.begin() + index;
|
||
while( ToBinary(it->mDistance) < maxDistBinary)
|
||
{
|
||
if( distance3DToleranceInULPs >= ToBinary((it->mPosition - pPosition).SquareLength()))
|
||
poResults.push_back(it->mIndex);
|
||
++it;
|
||
if( it == mPositions.end())
|
||
break;
|
||
}
|
||
|
||
// that's it
|
||
}
|
||
|
||
// ------------------------------------------------------------------------------------------------
|
||
unsigned int SpatialSort::GenerateMappingTable(std::vector<unsigned int>& fill,float pRadius) const
|
||
{
|
||
fill.resize(mPositions.size(),UINT_MAX);
|
||
float dist, maxDist;
|
||
|
||
unsigned int t=0;
|
||
const float pSquared = pRadius*pRadius;
|
||
for (size_t i = 0; i < mPositions.size();) {
|
||
dist = mPositions[i].mPosition * mPlaneNormal;
|
||
maxDist = dist + pRadius;
|
||
|
||
fill[mPositions[i].mIndex] = t;
|
||
const aiVector3D& oldpos = mPositions[i].mPosition;
|
||
for (++i; i < fill.size() && mPositions[i].mDistance < maxDist
|
||
&& (mPositions[i].mPosition - oldpos).SquareLength() < pSquared; ++i)
|
||
{
|
||
fill[mPositions[i].mIndex] = t;
|
||
}
|
||
++t;
|
||
}
|
||
|
||
#ifdef ASSIMP_BUILD_DEBUG
|
||
|
||
// debug invariant: mPositions[i].mIndex values must range from 0 to mPositions.size()-1
|
||
for (size_t i = 0; i < fill.size(); ++i) {
|
||
ai_assert(fill[i]<mPositions.size());
|
||
}
|
||
|
||
#endif
|
||
return t;
|
||
}
|
||
|