/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 | } |