SpatialSort.cpp 15 KB

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