| /* |
| --------------------------------------------------------------------------- |
| Open Asset Import Library (assimp) |
| --------------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, 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 <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 |
| ai_real distance = *vec * mPlaneNormal; |
| mPositions.push_back( Entry( static_cast<unsigned int>(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, |
| ai_real pRadius, std::vector<unsigned int>& poResults) const |
| { |
| const ai_real dist = pPosition * mPlaneNormal; |
| const ai_real minDist = dist - pRadius, maxDist = dist + pRadius; |
| |
| // clear the array |
| poResults.clear(); |
| |
| // 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 ai_real 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 ai_int BinFloat; |
| |
| // -------------------------------------------------------------------------------------------- |
| // Converts the bit pattern of a floating-point number to its signed integer representation. |
| BinFloat ToBinary( const ai_real & 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! |
| static_assert( sizeof(BinFloat) >= sizeof(ai_real), "sizeof(BinFloat) >= sizeof(ai_real)"); |
| |
| #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). |
| static_assert( sizeof(BinFloat) == sizeof(ai_real), "sizeof(BinFloat) == sizeof(ai_real)"); |
| |
| // 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 { |
| ai_real 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 identical 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.resize( 0 ); |
| |
| // 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, ai_real pRadius) const |
| { |
| fill.resize(mPositions.size(),UINT_MAX); |
| ai_real dist, maxDist; |
| |
| unsigned int t=0; |
| const ai_real 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; |
| } |