Coverage Report

Created: 2025-06-22 07:30

/src/assimp/code/Common/SpatialSort.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
---------------------------------------------------------------------------
3
Open Asset Import Library (assimp)
4
---------------------------------------------------------------------------
5
6
Copyright (c) 2006-2025, assimp team
7
8
All rights reserved.
9
10
Redistribution and use of this software in source and binary forms,
11
with or without modification, are permitted provided that the following
12
conditions are met:
13
14
* Redistributions of source code must retain the above
15
  copyright notice, this list of conditions and the
16
  following disclaimer.
17
18
* Redistributions in binary form must reproduce the above
19
  copyright notice, this list of conditions and the
20
  following disclaimer in the documentation and/or other
21
  materials provided with the distribution.
22
23
* Neither the name of the assimp team, nor the names of its
24
  contributors may be used to endorse or promote products
25
  derived from this software without specific prior
26
  written permission of the assimp team.
27
28
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
29
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
30
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
31
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
32
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
33
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
34
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
38
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39
---------------------------------------------------------------------------
40
*/
41
42
/** @file Implementation of the helper class to quickly find vertices close to a given position */
43
44
#include <assimp/SpatialSort.h>
45
#include <assimp/ai_assert.h>
46
47
using namespace Assimp;
48
49
// CHAR_BIT seems to be defined under MVSC, but not under GCC. Pray that the correct value is 8.
50
#ifndef CHAR_BIT
51
#define CHAR_BIT 8
52
#endif
53
54
const aiVector3D PlaneInit(0.8523f, 0.34321f, 0.5736f);
55
56
// ------------------------------------------------------------------------------------------------
57
// Constructs a spatially sorted representation from the given position array.
58
// define the reference plane. We choose some arbitrary vector away from all basic axes
59
// in the hope that no model spreads all its vertices along this plane.
60
SpatialSort::SpatialSort(const aiVector3D *pPositions, unsigned int pNumPositions, unsigned int pElementOffset) :
61
0
        mPlaneNormal(PlaneInit),
62
0
        mFinalized(false) {
63
0
    mPlaneNormal.Normalize();
64
0
    Fill(pPositions, pNumPositions, pElementOffset);
65
0
}
66
67
// ------------------------------------------------------------------------------------------------
68
SpatialSort::SpatialSort() :
69
9.92k
        mPlaneNormal(PlaneInit),
70
9.92k
        mFinalized(false) {
71
9.92k
    mPlaneNormal.Normalize();
72
9.92k
}
73
74
// ------------------------------------------------------------------------------------------------
75
void SpatialSort::Fill(const aiVector3D *pPositions, unsigned int pNumPositions,
76
        unsigned int pElementOffset,
77
5.54k
        bool pFinalize /*= true */) {
78
5.54k
    mPositions.clear();
79
5.54k
    mFinalized = false;
80
5.54k
    Append(pPositions, pNumPositions, pElementOffset, pFinalize);
81
5.54k
    mFinalized = pFinalize;
82
5.54k
}
83
84
// ------------------------------------------------------------------------------------------------
85
1.04M
ai_real SpatialSort::CalculateDistance(const aiVector3D &pPosition) const {
86
1.04M
    return (pPosition - mCentroid) * mPlaneNormal;
87
1.04M
}
88
89
// ------------------------------------------------------------------------------------------------
90
5.54k
void SpatialSort::Finalize() {
91
5.54k
    const ai_real scale = 1.0f / mPositions.size();
92
1.04M
    for (unsigned int i = 0; i < mPositions.size(); i++) {
93
1.03M
        mCentroid += scale * mPositions[i].mPosition;
94
1.03M
    }
95
1.04M
    for (unsigned int i = 0; i < mPositions.size(); i++) {
96
1.03M
        mPositions[i].mDistance = CalculateDistance(mPositions[i].mPosition);
97
1.03M
    }
98
5.54k
    std::sort(mPositions.begin(), mPositions.end());
99
5.54k
    mFinalized = true;
100
5.54k
}
101
102
// ------------------------------------------------------------------------------------------------
103
void SpatialSort::Append(const aiVector3D *pPositions, unsigned int pNumPositions,
104
        unsigned int pElementOffset,
105
5.54k
        bool pFinalize /*= true */) {
106
5.54k
    ai_assert(!mFinalized && "You cannot add positions to the SpatialSort object after it has been finalized.");
107
    // store references to all given positions along with their distance to the reference plane
108
5.54k
    const size_t initial = mPositions.size();
109
5.54k
    mPositions.reserve(initial + pNumPositions);
110
1.04M
    for (unsigned int a = 0; a < pNumPositions; a++) {
111
1.03M
        const char *tempPointer = reinterpret_cast<const char *>(pPositions);
112
1.03M
        const aiVector3D *vec = reinterpret_cast<const aiVector3D *>(tempPointer + a * pElementOffset);
113
1.03M
        mPositions.emplace_back(static_cast<unsigned int>(a + initial), *vec);
114
1.03M
    }
115
116
5.54k
    if (pFinalize) {
117
        // now sort the array ascending by distance.
118
5.54k
        Finalize();
119
5.54k
    }
120
5.54k
}
121
122
// ------------------------------------------------------------------------------------------------
123
// Returns an iterator for all positions close to the given position.
124
void SpatialSort::FindPositions(const aiVector3D &pPosition,
125
11.1k
        ai_real pRadius, std::vector<unsigned int> &poResults) const {
126
11.1k
    ai_assert(mFinalized && "The SpatialSort object must be finalized before FindPositions can be called.");
127
11.1k
    const ai_real dist = CalculateDistance(pPosition);
128
11.1k
    const ai_real minDist = dist - pRadius, maxDist = dist + pRadius;
129
130
    // clear the array
131
11.1k
    poResults.clear();
132
133
    // quick check for positions outside the range
134
11.1k
    if (mPositions.size() == 0)
135
0
        return;
136
11.1k
    if (maxDist < mPositions.front().mDistance)
137
0
        return;
138
11.1k
    if (minDist > mPositions.back().mDistance)
139
0
        return;
140
141
    // do a binary search for the minimal distance to start the iteration there
142
11.1k
    unsigned int index = (unsigned int)mPositions.size() / 2;
143
11.1k
    unsigned int binaryStepSize = (unsigned int)mPositions.size() / 4;
144
24.5k
    while (binaryStepSize > 1) {
145
13.4k
        if (mPositions[index].mDistance < minDist)
146
5.26k
            index += binaryStepSize;
147
8.15k
        else
148
8.15k
            index -= binaryStepSize;
149
150
13.4k
        binaryStepSize /= 2;
151
13.4k
    }
152
153
    // depending on the direction of the last step we need to single step a bit back or forth
154
    // to find the actual beginning element of the range
155
27.9k
    while (index > 0 && mPositions[index].mDistance > minDist)
156
16.8k
        index--;
157
20.7k
    while (index < (mPositions.size() - 1) && mPositions[index].mDistance < minDist)
158
9.68k
        index++;
159
160
    // Mow start iterating from there until the first position lays outside of the distance range.
161
    // Add all positions inside the distance range within the given radius to the result array
162
11.1k
    std::vector<Entry>::const_iterator it = mPositions.begin() + index;
163
11.1k
    const ai_real pSquared = pRadius * pRadius;
164
973k
    while (it->mDistance < maxDist) {
165
966k
        if ((it->mPosition - pPosition).SquareLength() < pSquared)
166
928k
            poResults.push_back(it->mIndex);
167
966k
        ++it;
168
966k
        if (it == mPositions.end())
169
4.45k
            break;
170
966k
    }
171
172
    // that's it
173
11.1k
}
174
175
namespace {
176
177
// Binary, signed-integer representation of a single-precision floating-point value.
178
// IEEE 754 says: "If two floating-point numbers in the same format are ordered then they are
179
//  ordered the same way when their bits are reinterpreted as sign-magnitude integers."
180
// This allows us to convert all floating-point numbers to signed integers of arbitrary size
181
//  and then use them to work with ULPs (Units in the Last Place, for high-precision
182
//  computations) or to compare them (integer comparisons are faster than floating-point
183
//  comparisons on many platforms).
184
typedef ai_int BinFloat;
185
186
// --------------------------------------------------------------------------------------------
187
// Converts the bit pattern of a floating-point number to its signed integer representation.
188
0
BinFloat ToBinary(const ai_real &pValue) {
189
190
    // If this assertion fails, signed int is not big enough to store a float on your platform.
191
    //  Please correct the declaration of BinFloat a few lines above - but do it in a portable,
192
    //  #ifdef'd manner!
193
0
    static_assert(sizeof(BinFloat) >= sizeof(ai_real), "sizeof(BinFloat) >= sizeof(ai_real)");
194
195
#if defined(_MSC_VER)
196
    // If this assertion fails, Visual C++ has finally moved to ILP64. This means that this
197
    //  code has just become legacy code! Find out the current value of _MSC_VER and modify
198
    //  the #if above so it evaluates false on the current and all upcoming VC versions (or
199
    //  on the current platform, if LP64 or LLP64 are still used on other platforms).
200
    static_assert(sizeof(BinFloat) == sizeof(ai_real), "sizeof(BinFloat) == sizeof(ai_real)");
201
202
    // This works best on Visual C++, but other compilers have their problems with it.
203
    const BinFloat binValue = reinterpret_cast<BinFloat const &>(pValue);
204
    //::memcpy(&binValue, &pValue, sizeof(pValue));
205
    //return binValue;
206
#else
207
    // On many compilers, reinterpreting a float address as an integer causes aliasing
208
    // problems. This is an ugly but more or less safe way of doing it.
209
0
    union {
210
0
        ai_real asFloat;
211
0
        BinFloat asBin;
212
0
    } conversion;
213
0
    conversion.asBin = 0; // zero empty space in case sizeof(BinFloat) > sizeof(float)
214
0
    conversion.asFloat = pValue;
215
0
    const BinFloat binValue = conversion.asBin;
216
0
#endif
217
218
    // floating-point numbers are of sign-magnitude format, so find out what signed number
219
    //  representation we must convert negative values to.
220
    // See http://en.wikipedia.org/wiki/Signed_number_representations.
221
0
    const BinFloat mask = BinFloat(1) << (CHAR_BIT * sizeof(BinFloat) - 1);
222
223
    // Two's complement?
224
0
    const bool DefaultValue = ((-42 == (~42 + 1)) && (binValue & mask));
225
0
    const bool OneComplement = ((-42 == ~42) && (binValue & mask));
226
227
0
    if (DefaultValue)
228
0
        return mask - binValue;
229
    // One's complement?
230
0
    else if (OneComplement)
231
0
        return BinFloat(-0) - binValue;
232
    // Sign-magnitude? -0 = 1000... binary
233
0
    return binValue;
234
0
}
235
236
} // namespace
237
238
// ------------------------------------------------------------------------------------------------
239
// Fills an array with indices of all positions identical to the given position. In opposite to
240
// FindPositions(), not an epsilon is used but a (very low) tolerance of four floating-point units.
241
0
void SpatialSort::FindIdenticalPositions(const aiVector3D &pPosition, std::vector<unsigned int> &poResults) const {
242
0
    ai_assert(mFinalized && "The SpatialSort object must be finalized before FindIdenticalPositions can be called.");
243
    // Epsilons have a huge disadvantage: they are of constant precision, while floating-point
244
    //  values are of log2 precision. If you apply e=0.01 to 100, the epsilon is rather small, but
245
    //  if you apply it to 0.001, it is enormous.
246
247
    // The best way to overcome this is the unit in the last place (ULP). A precision of 2 ULPs
248
    //  tells us that a float does not differ more than 2 bits from the "real" value. ULPs are of
249
    //  logarithmic precision - around 1, they are 1*(2^24) and around 10000, they are 0.00125.
250
251
    // For standard C math, we can assume a precision of 0.5 ULPs according to IEEE 754. The
252
    //  incoming vertex positions might have already been transformed, probably using rather
253
    //  inaccurate SSE instructions, so we assume a tolerance of 4 ULPs to safely identify
254
    //  identical vertex positions.
255
0
    static const int toleranceInULPs = 4;
256
    // An interesting point is that the inaccuracy grows linear with the number of operations:
257
    //  multiplying to numbers, each inaccurate to four ULPs, results in an inaccuracy of four ULPs
258
    //  plus 0.5 ULPs for the multiplication.
259
    // To compute the distance to the plane, a dot product is needed - that is a multiplication and
260
    //  an addition on each number.
261
0
    static const int distanceToleranceInULPs = toleranceInULPs + 1;
262
    // The squared distance between two 3D vectors is computed the same way, but with an additional
263
    //  subtraction.
264
0
    static const int distance3DToleranceInULPs = distanceToleranceInULPs + 1;
265
266
    // Convert the plane distance to its signed integer representation so the ULPs tolerance can be
267
    //  applied. For some reason, VC won't optimize two calls of the bit pattern conversion.
268
0
    const BinFloat minDistBinary = ToBinary(CalculateDistance(pPosition)) - distanceToleranceInULPs;
269
0
    const BinFloat maxDistBinary = minDistBinary + 2 * distanceToleranceInULPs;
270
271
    // clear the array in this strange fashion because a simple clear() would also deallocate
272
    // the array which we want to avoid
273
0
    poResults.resize(0);
274
275
    // do a binary search for the minimal distance to start the iteration there
276
0
    unsigned int index = (unsigned int)mPositions.size() / 2;
277
0
    unsigned int binaryStepSize = (unsigned int)mPositions.size() / 4;
278
0
    while (binaryStepSize > 1) {
279
        // Ugly, but conditional jumps are faster with integers than with floats
280
0
        if (minDistBinary > ToBinary(mPositions[index].mDistance))
281
0
            index += binaryStepSize;
282
0
        else
283
0
            index -= binaryStepSize;
284
285
0
        binaryStepSize /= 2;
286
0
    }
287
288
    // depending on the direction of the last step we need to single step a bit back or forth
289
    // to find the actual beginning element of the range
290
0
    while (index > 0 && minDistBinary < ToBinary(mPositions[index].mDistance))
291
0
        index--;
292
0
    while (index < (mPositions.size() - 1) && minDistBinary > ToBinary(mPositions[index].mDistance))
293
0
        index++;
294
295
    // Now start iterating from there until the first position lays outside of the distance range.
296
    // Add all positions inside the distance range within the tolerance to the result array
297
0
    std::vector<Entry>::const_iterator it = mPositions.begin() + index;
298
0
    while (ToBinary(it->mDistance) < maxDistBinary) {
299
0
        if (distance3DToleranceInULPs >= ToBinary((it->mPosition - pPosition).SquareLength()))
300
0
            poResults.push_back(it->mIndex);
301
0
        ++it;
302
0
        if (it == mPositions.end())
303
0
            break;
304
0
    }
305
306
    // that's it
307
0
}
308
309
// ------------------------------------------------------------------------------------------------
310
0
unsigned int SpatialSort::GenerateMappingTable(std::vector<unsigned int> &fill, ai_real pRadius) const {
311
0
    ai_assert(mFinalized && "The SpatialSort object must be finalized before GenerateMappingTable can be called.");
312
0
    fill.resize(mPositions.size(), UINT_MAX);
313
0
    ai_real dist, maxDist;
314
315
0
    unsigned int t = 0;
316
0
    const ai_real pSquared = pRadius * pRadius;
317
0
    for (size_t i = 0; i < mPositions.size();) {
318
0
        dist = (mPositions[i].mPosition - mCentroid) * mPlaneNormal;
319
0
        maxDist = dist + pRadius;
320
321
0
        fill[mPositions[i].mIndex] = t;
322
0
        const aiVector3D &oldpos = mPositions[i].mPosition;
323
0
        for (++i; i < fill.size() && mPositions[i].mDistance < maxDist && (mPositions[i].mPosition - oldpos).SquareLength() < pSquared; ++i) {
324
0
            fill[mPositions[i].mIndex] = t;
325
0
        }
326
0
        ++t;
327
0
    }
328
329
0
#ifdef ASSIMP_BUILD_DEBUG
330
331
    // debug invariant: mPositions[i].mIndex values must range from 0 to mPositions.size()-1
332
0
    for (size_t i = 0; i < fill.size(); ++i) {
333
0
        ai_assert(fill[i] < mPositions.size());
334
0
    }
335
336
0
#endif
337
0
    return t;
338
0
}