summaryrefslogtreecommitdiff
path: root/thirdparty/assimp/code/SpatialSort.cpp
blob: a4f3a4e4b851d9fa0db444527dee7c98b4f7e798 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/*
---------------------------------------------------------------------------
Open Asset Import Library (assimp)
---------------------------------------------------------------------------

Copyright (c) 2006-2019, 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 <assimp/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 array
    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;
}