Ver código fonte

Merge branch 'master' into Artenuvielle-x3d_pugi_migration_artenuvielle

Kim Kulling 3 anos atrás
pai
commit
9f28ef4c25

+ 1 - 1
CMakeLists.txt

@@ -53,7 +53,7 @@ IF(ASSIMP_HUNTER_ENABLED)
   add_definitions(-DASSIMP_USE_HUNTER)
 ENDIF()
 
-PROJECT( Assimp VERSION 5.0.1 )
+PROJECT(Assimp VERSION 5.1.0)
 
 # All supported options ###############################################
 

+ 1 - 0
Readme.md

@@ -43,6 +43,7 @@ Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file.
 * [Pascal](port/AssimpPascal/Readme.md)
 * [Javascript (Alpha)](https://github.com/makc/assimp2json)
 * [Unity 3d Plugin](https://ricardoreis.net/trilib-2/)
+* [Unreal Engine Plugin](https://github.com/irajsb/UE4_Assimp/)
 * [JVM](https://github.com/kotlin-graphics/assimp) Full jvm port (current [status](https://github.com/kotlin-graphics/assimp/wiki/Status))
 * [HAXE-Port](https://github.com/longde123/assimp-haxe) The Assimp-HAXE-port.
 * [Rust](https://github.com/jkvargas/russimp)

+ 1 - 1
code/AssetLib/3MF/D3MFOpcPackage.cpp

@@ -149,7 +149,7 @@ D3MFOpcPackage::D3MFOpcPackage(IOSystem *pIOHandler, const std::string &rFile) :
 
             IOStream *fileStream = mZipArchive->Open(file.c_str());
             if (nullptr == fileStream) {
-                ai_assert(fileStream != nullptr);
+                ASSIMP_LOG_ERROR("Filestream is nullptr.");
                 continue;
             }
 

+ 0 - 1
code/AssetLib/3MF/XmlSerializer.cpp

@@ -159,7 +159,6 @@ bool parseColor(const char *color, aiColor4D &diffuse) {
         return false;
     }
 
-    //const char *buf(color);
     if ('#' != color[0]) {
         return false;
     }

+ 1 - 0
code/AssetLib/HMP/HMPLoader.cpp

@@ -451,6 +451,7 @@ void HMPImporter::ReadFirstSkin(unsigned int iNumSkins, const unsigned char *szC
 
     // now we need to skip any other skins ...
     for (unsigned int i = 1; i < iNumSkins; ++i) {
+        SizeCheck(szCursor + 3 * sizeof(uint32_t));
         iType = *((uint32_t *)szCursor);
         szCursor += sizeof(uint32_t);
         iWidth = *((uint32_t *)szCursor);

+ 1 - 1
code/AssetLib/M3D/m3d.h

@@ -896,7 +896,7 @@ char *_m3d_safestr(char *in, int morelines) {
         if (!out) return NULL;
         while (*i == ' ' || *i == '\t' || *i == '\r' || (morelines && *i == '\n'))
             i++;
-        for (; *i && (morelines || (*i != '\r' && *i != '\n')); i++) {
+        for (; *i && (morelines || (*i != '\r' && *i != '\n')) && o - out < l; i++) {
             if (*i == '\r') continue;
             if (*i == '\n') {
                 if (morelines >= 3 && o > out && *(o - 1) == '\n') break;

+ 1 - 1
code/AssetLib/MDL/MDLLoader.cpp

@@ -600,7 +600,7 @@ void MDLImporter::InternReadFile_3DGS_MDL345() {
 
     // need to read all textures
     for (unsigned int i = 0; i < (unsigned int)pcHeader->num_skins; ++i) {
-        if (szCurrent >= szEnd) {
+        if (szCurrent + sizeof(uint32_t) > szEnd) {
             throw DeadlyImportError("Texture data past end of file.");
         }
         BE_NCONST MDL::Skin *pcSkin;

+ 6 - 0
code/AssetLib/MDL/MDLMaterialLoader.cpp

@@ -132,6 +132,9 @@ void MDLImporter::CreateTextureARGB8_3DGS_MDL3(const unsigned char *szData) {
     pcNew->mWidth = pcHeader->skinwidth;
     pcNew->mHeight = pcHeader->skinheight;
 
+    if(pcNew->mWidth != 0 && pcNew->mHeight > UINT_MAX/pcNew->mWidth) {
+        throw DeadlyImportError("Invalid MDL file. A texture is too big.");
+    }
     pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight];
 
     const unsigned char *szColorMap;
@@ -217,6 +220,9 @@ void MDLImporter::ParseTextureColorData(const unsigned char *szData,
 
     // allocate storage for the texture image
     if (do_read) {
+        if(pcNew->mWidth != 0 && pcNew->mHeight > UINT_MAX/pcNew->mWidth) {
+            throw DeadlyImportError("Invalid MDL file. A texture is too big.");
+        }
         pcNew->pcData = new aiTexel[pcNew->mWidth * pcNew->mHeight];
     }
 

+ 1 - 1
code/AssetLib/STEPParser/STEPFileReader.cpp

@@ -325,7 +325,7 @@ std::shared_ptr<const EXPRESS::DataType> EXPRESS::DataType::Parse(const char*& i
                 std::transform(s.begin(),s.end(),s.begin(),&ai_tolower<char> );
                 if (schema->IsKnownToken(s)) {
                     for(cur = t+1;*cur++ != '(';);
-                    const std::shared_ptr<const EXPRESS::DataType> dt = Parse(cur);
+                    std::shared_ptr<const EXPRESS::DataType> dt = Parse(cur);
                     inout = *cur ? cur+1 : cur;
                     return dt;
                 }

+ 3 - 2
code/Common/FileSystemFilter.h

@@ -300,13 +300,14 @@ private:
 
         const char separator = getOsSeparator();
         for (it = in.begin(); it != in.end(); ++it) {
+            int remaining = std::distance(in.end(), it);
             // Exclude :// and \\, which remain untouched.
             // https://sourceforge.net/tracker/?func=detail&aid=3031725&group_id=226462&atid=1067632
-            if ( !strncmp(&*it, "://", 3 )) {
+            if (remaining >= 3 && !strncmp(&*it, "://", 3 )) {
                 it += 3;
                 continue;
             }
-            if (it == in.begin() && !strncmp(&*it, "\\\\", 2)) {
+            if (it == in.begin() && remaining >= 2 && !strncmp(&*it, "\\\\", 2)) {
                 it += 2;
                 continue;
             }

+ 16 - 8
code/Common/RemoveComments.cpp

@@ -64,20 +64,28 @@ void CommentRemover::RemoveLineComments(const char* szComment,
     if (len > lenBuffer) {
         len = lenBuffer;
     }
-    while (*szBuffer)   {
+
+    char *szCurrent = szBuffer;
+    while (*szCurrent)   {
 
         // skip over quotes
-        if (*szBuffer == '\"' || *szBuffer == '\'')
-            while (*szBuffer++ && *szBuffer != '\"' && *szBuffer != '\'');
-        if (!strncmp(szBuffer,szComment,len)) {
-            while (!IsLineEnd(*szBuffer))
-                *szBuffer++ = chReplacement;
+        if (*szCurrent == '\"' || *szCurrent == '\'')
+            while (*szCurrent++ && *szCurrent != '\"' && *szCurrent != '\'');
 
-            if (!*szBuffer) {
+        size_t lenRemaining = lenBuffer - (szCurrent - szBuffer);
+        if(lenRemaining < len) {
+            break;
+        }
+
+        if (!strncmp(szCurrent,szComment,len)) {
+            while (!IsLineEnd(*szCurrent))
+                *szCurrent++ = chReplacement;
+
+            if (!*szCurrent) {
                 break;
             }
         }
-        ++szBuffer;
+        ++szCurrent;
     }
 }
 

+ 28 - 10
code/Common/SpatialSort.cpp

@@ -58,14 +58,16 @@ const aiVector3D PlaneInit(0.8523f, 0.34321f, 0.5736f);
 // define the reference plane. We choose some arbitrary vector away from all basic axes
 // in the hope that no model spreads all its vertices along this plane.
 SpatialSort::SpatialSort(const aiVector3D *pPositions, unsigned int pNumPositions, unsigned int pElementOffset) :
-        mPlaneNormal(PlaneInit) {
+        mPlaneNormal(PlaneInit),
+        mFinalized(false) {
     mPlaneNormal.Normalize();
     Fill(pPositions, pNumPositions, pElementOffset);
 }
 
 // ------------------------------------------------------------------------------------------------
 SpatialSort::SpatialSort() :
-        mPlaneNormal(PlaneInit) {
+        mPlaneNormal(PlaneInit),
+        mFinalized(false) {
     mPlaneNormal.Normalize();
 }
 
@@ -80,28 +82,41 @@ void SpatialSort::Fill(const aiVector3D *pPositions, unsigned int pNumPositions,
         unsigned int pElementOffset,
         bool pFinalize /*= true */) {
     mPositions.clear();
+    mFinalized = false;
     Append(pPositions, pNumPositions, pElementOffset, pFinalize);
+    mFinalized = pFinalize;
+}
+
+// ------------------------------------------------------------------------------------------------
+ai_real SpatialSort::CalculateDistance(const aiVector3D &pPosition) const {
+    return (pPosition - mCentroid) * mPlaneNormal;
 }
 
 // ------------------------------------------------------------------------------------------------
 void SpatialSort::Finalize() {
+    const ai_real scale = 1.0f / mPositions.size();
+    for (unsigned int i = 0; i < mPositions.size(); i++) {
+        mCentroid += scale * mPositions[i].mPosition; 
+    }
+    for (unsigned int i = 0; i < mPositions.size(); i++) {
+        mPositions[i].mDistance = CalculateDistance(mPositions[i].mPosition);
+    }
     std::sort(mPositions.begin(), mPositions.end());
+    mFinalized = true;
 }
 
 // ------------------------------------------------------------------------------------------------
 void SpatialSort::Append(const aiVector3D *pPositions, unsigned int pNumPositions,
         unsigned int pElementOffset,
         bool pFinalize /*= true */) {
+    ai_assert(!mFinalized && "You cannot add positions to the SpatialSort object after it has been finalized.");
     // 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));
+    mPositions.reserve(initial + pNumPositions);
     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));
+        mPositions.push_back(Entry(static_cast<unsigned int>(a + initial), *vec));
     }
 
     if (pFinalize) {
@@ -114,7 +129,8 @@ void SpatialSort::Append(const aiVector3D *pPositions, unsigned int pNumPosition
 // 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;
+    ai_assert(mFinalized && "The SpatialSort object must be finalized before FindPositions can be called.");
+    const ai_real dist = CalculateDistance(pPosition);
     const ai_real minDist = dist - pRadius, maxDist = dist + pRadius;
 
     // clear the array
@@ -229,6 +245,7 @@ BinFloat ToBinary(const ai_real &pValue) {
 // 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 {
+    ai_assert(mFinalized && "The SpatialSort object must be finalized before FindIdenticalPositions can be called.");
     // 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.
@@ -254,7 +271,7 @@ void SpatialSort::FindIdenticalPositions(const aiVector3D &pPosition, std::vecto
 
     // 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 minDistBinary = ToBinary(CalculateDistance(pPosition)) - distanceToleranceInULPs;
     const BinFloat maxDistBinary = minDistBinary + 2 * distanceToleranceInULPs;
 
     // clear the array in this strange fashion because a simple clear() would also deallocate
@@ -297,13 +314,14 @@ void SpatialSort::FindIdenticalPositions(const aiVector3D &pPosition, std::vecto
 
 // ------------------------------------------------------------------------------------------------
 unsigned int SpatialSort::GenerateMappingTable(std::vector<unsigned int> &fill, ai_real pRadius) const {
+    ai_assert(mFinalized && "The SpatialSort object must be finalized before GenerateMappingTable can be called.");
     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;
+        dist = (mPositions[i].mPosition - mCentroid) * mPlaneNormal;
         maxDist = dist + pRadius;
 
         fill[mPositions[i].mIndex] = t;

+ 12 - 8
contrib/openddlparser/code/OpenDDLParser.cpp

@@ -292,12 +292,15 @@ char *OpenDDLParser::parseHeader(char *in, char *end) {
 
         Property *first(nullptr);
         in = lookForNextToken(in, end);
-        if (*in == Grammar::OpenPropertyToken[0]) {
+        if (in != end && *in == Grammar::OpenPropertyToken[0]) {
             in++;
             Property *prop(nullptr), *prev(nullptr);
-            while (*in != Grammar::ClosePropertyToken[0] && in != end) {
+            while (in != end && *in != Grammar::ClosePropertyToken[0]) {
                 in = OpenDDLParser::parseProperty(in, end, &prop);
                 in = lookForNextToken(in, end);
+                if(in == end) {
+                    break;
+                }
 
                 if (*in != Grammar::CommaSeparator[0] && *in != Grammar::ClosePropertyToken[0]) {
                     logInvalidTokenError(in, Grammar::ClosePropertyToken, m_logCallback);
@@ -314,7 +317,9 @@ char *OpenDDLParser::parseHeader(char *in, char *end) {
                     prev = prop;
                 }
             }
-            ++in;
+            if(in != end) {
+                ++in;
+            }
         }
 
         // set the properties
@@ -479,7 +484,7 @@ void OpenDDLParser::normalizeBuffer(std::vector<char> &buffer) {
         // check for a comment
         if (isCommentOpenTag(c, end)) {
             ++readIdx;
-            while (!isCommentCloseTag(&buffer[readIdx], end)) {
+            while (readIdx < len && !isCommentCloseTag(&buffer[readIdx], end)) {
                 ++readIdx;
             }
             ++readIdx;
@@ -489,7 +494,7 @@ void OpenDDLParser::normalizeBuffer(std::vector<char> &buffer) {
             if (isComment<char>(c, end)) {
                 ++readIdx;
                 // skip the comment and the rest of the line
-                while (!isEndofLine(buffer[readIdx])) {
+                while (readIdx < len && !isEndofLine(buffer[readIdx])) {
                     ++readIdx;
                 }
             }
@@ -548,8 +553,7 @@ char *OpenDDLParser::parseIdentifier(char *in, char *end, Text **id) {
     // get size of id
     size_t idLen(0);
     char *start(in);
-    while (!isSeparator(*in) &&
-            !isNewLine(*in) && (in != end) &&
+    while ((in != end) && !isSeparator(*in) && !isNewLine(*in) &&
             *in != Grammar::OpenPropertyToken[0] &&
             *in != Grammar::ClosePropertyToken[0] &&
             *in != '$') {
@@ -861,7 +865,7 @@ char *OpenDDLParser::parseProperty(char *in, char *end, Property **prop) {
     in = parseIdentifier(in, end, &id);
     if (nullptr != id) {
         in = lookForNextToken(in, end);
-        if (*in == '=') {
+        if (in != end && *in == '=') {
             ++in;
             in = getNextToken(in, end);
             Value *primData(nullptr);

+ 2 - 1
contrib/openddlparser/include/openddlparser/OpenDDLParserUtils.h

@@ -318,7 +318,8 @@ static const unsigned char chartype_table[256] = {
 
 template <class T>
 inline bool isNumeric(const T in) {
-    return (chartype_table[static_cast<size_t>(in)] == 1);
+    size_t idx = static_cast<size_t>(in);
+    return idx < sizeof(chartype_table) && (chartype_table[idx] == 1);
 }
 
 template <class T>

+ 10 - 0
include/assimp/IOStreamBuffer.h

@@ -261,6 +261,11 @@ AI_FORCE_INLINE bool IOStreamBuffer<T>::getNextDataLine(std::vector<T> &buffer,
         buffer[i] = m_cache[m_cachePos];
         ++m_cachePos;
         ++i;
+
+        if(i == buffer.size()) {
+            buffer.resize(buffer.size() * 2);
+        }
+
         if (m_cachePos >= size()) {
             break;
         }
@@ -308,6 +313,11 @@ AI_FORCE_INLINE bool IOStreamBuffer<T>::getNextLine(std::vector<T> &buffer) {
         buffer[i] = m_cache[m_cachePos];
         ++m_cachePos;
         ++i;
+
+        if(i == buffer.size()) {
+            buffer.resize(buffer.size() * 2);
+        }
+
         if (m_cachePos >= m_cacheSize) {
             if (!readNextBlock()) {
                 return false;

+ 21 - 6
include/assimp/SpatialSort.h

@@ -51,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <assimp/types.h>
 #include <vector>
+#include <limits>
 
 namespace Assimp {
 
@@ -142,24 +143,35 @@ public:
             ai_real pRadius) const;
 
 protected:
-    /** Normal of the sorting plane, normalized. The center is always at (0, 0, 0) */
+    /** Return the distance to the sorting plane. */
+    ai_real CalculateDistance(const aiVector3D &pPosition) const;
+
+protected:
+    /** Normal of the sorting plane, normalized.
+     */
     aiVector3D mPlaneNormal;
 
+    /** The centroid of the positions, which is used as a point on the sorting plane
+     * when calculating distance. This value is calculated in Finalize.
+    */
+    aiVector3D mCentroid;
+
     /** An entry in a spatially sorted position array. Consists of a vertex index,
      * its position and its pre-calculated distance from the reference plane */
     struct Entry {
         unsigned int mIndex; ///< The vertex referred by this entry
         aiVector3D mPosition; ///< Position
-        ai_real mDistance; ///< Distance of this vertex to the sorting plane
+        /// Distance of this vertex to the sorting plane. This is set by Finalize.
+        ai_real mDistance; 
 
         Entry() AI_NO_EXCEPT
-                : mIndex(999999999),
+                : mIndex(std::numeric_limits<unsigned int>::max()),
                   mPosition(),
-                  mDistance(99999.) {
+                  mDistance(std::numeric_limits<ai_real>::max()) {
             // empty
         }
-        Entry(unsigned int pIndex, const aiVector3D &pPosition, ai_real pDistance) :
-                mIndex(pIndex), mPosition(pPosition), mDistance(pDistance) {
+        Entry(unsigned int pIndex, const aiVector3D &pPosition) :
+                mIndex(pIndex), mPosition(pPosition), mDistance(std::numeric_limits<ai_real>::max()) {
             // empty
         }
 
@@ -168,6 +180,9 @@ protected:
 
     // all positions, sorted by distance to the sorting plane
     std::vector<Entry> mPositions;
+
+    /// false until the Finalize method is called.
+    bool mFinalized;
 };
 
 } // end of namespace Assimp

+ 3 - 0
include/assimp/anim.h

@@ -98,6 +98,7 @@ struct aiVectorKey {
     bool operator<(const aiVectorKey &rhs) const {
         return mTime < rhs.mTime;
     }
+    
     bool operator>(const aiVectorKey &rhs) const {
         return mTime > rhs.mTime;
     }
@@ -131,6 +132,7 @@ struct aiQuatKey {
     bool operator==(const aiQuatKey &rhs) const {
         return rhs.mValue == this->mValue;
     }
+    
     bool operator!=(const aiQuatKey &rhs) const {
         return rhs.mValue != this->mValue;
     }
@@ -139,6 +141,7 @@ struct aiQuatKey {
     bool operator<(const aiQuatKey &rhs) const {
         return mTime < rhs.mTime;
     }
+    
     bool operator>(const aiQuatKey &rhs) const {
         return mTime > rhs.mTime;
     }

+ 8 - 0
include/assimp/mesh.h

@@ -300,6 +300,10 @@ struct aiBone {
     aiBone() AI_NO_EXCEPT
             : mName(),
               mNumWeights(0),
+#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS
+              mArmature(nullptr),
+              mNode(nullptr),
+#endif
               mWeights(nullptr),
               mOffsetMatrix() {
         // empty
@@ -309,6 +313,10 @@ struct aiBone {
     aiBone(const aiBone &other) :
             mName(other.mName),
             mNumWeights(other.mNumWeights),
+#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS
+              mArmature(nullptr),
+              mNode(nullptr),
+#endif
             mWeights(nullptr),
             mOffsetMatrix(other.mOffsetMatrix) {
         if (other.mWeights && other.mNumWeights) {

+ 11 - 15
include/assimp/port/AndroidJNI/AndroidJNIIOSystem.h

@@ -2,7 +2,7 @@
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2021, assimp team
 All rights reserved.
 
 Redistribution and use of this software in source and binary forms,
@@ -54,36 +54,32 @@ namespace Assimp	{
 
 // ---------------------------------------------------------------------------
 /** Android extension to DefaultIOSystem using the standard C file functions */
-class ASSIMP_API AndroidJNIIOSystem : public DefaultIOSystem
-{
+class ASSIMP_API AndroidJNIIOSystem : public DefaultIOSystem {
 public:
-
 	/** Initialize android activity data */
 	std::string mApkWorkspacePath;
 	AAssetManager* mApkAssetManager;
 
-	/** Constructor. */
+	/// Constructor.
 	AndroidJNIIOSystem(ANativeActivity* activity);
 
-	/** Destructor. */
+    /// Class constructor with past and asset manager.
+	AndroidJNIIOSystem(const char *internalPath, AAssetManager* assetManager);
+
+	/// Destructor.
 	~AndroidJNIIOSystem();
 
-	// -------------------------------------------------------------------
-	/** Tests for the existence of a file at the given path. */
+	/// Tests for the existence of a file at the given path.
 	bool Exists( const char* pFile) const;
 
-	// -------------------------------------------------------------------
-	/** Opens a file at the given path, with given mode */
+	/// Opens a file at the given path, with given mode
 	IOStream* Open( const char* strFile, const char* strMode);
 
-	// ------------------------------------------------------------------------------------------------
-	// Inits Android extractor
+	/// Inits Android extractor
 	void AndroidActivityInit(ANativeActivity* activity);
 
-	// ------------------------------------------------------------------------------------------------
-	// Extracts android asset
+	/// Extracts android asset
 	bool AndroidExtractAsset(std::string name);
-
 };
 
 } //!ns Assimp

+ 95 - 92
port/AndroidJNI/AndroidJNIIOSystem.cpp

@@ -3,7 +3,7 @@
 Open Asset Import Library (assimp)
 ---------------------------------------------------------------------------
 
-Copyright (c) 2006-2020, assimp team
+Copyright (c) 2006-2021, assimp team
 
 All rights reserved.
 
@@ -67,45 +67,50 @@ AndroidJNIIOSystem::AndroidJNIIOSystem(ANativeActivity* activity)
 	AndroidActivityInit(activity);
 }
 
+AndroidJNIIOSystem::AndroidJNIIOSystem(const char *internalPath, AAssetManager* assetManager) :
+        mApkWorkspacePath(internalPath),
+        mApkAssetManager(assetManager) {
+    // empty
+}
+
 // ------------------------------------------------------------------------------------------------
 // Destructor.
-AndroidJNIIOSystem::~AndroidJNIIOSystem()
-{
-	// nothing to do here
+AndroidJNIIOSystem::~AndroidJNIIOSystem() {
+    // nothing to do here
 }
 
 // ------------------------------------------------------------------------------------------------
 // Tests for the existence of a file at the given path.
-bool AndroidJNIIOSystem::Exists( const char* pFile) const
-{
-	AAsset* asset = AAssetManager_open(mApkAssetManager, pFile,
-			AASSET_MODE_UNKNOWN);
-	FILE* file = ::fopen( (mApkWorkspacePath + getOsSeparator() + std::string(pFile)).c_str(), "rb");
-
-	if (!asset && !file)
-		{
-		__android_log_print(ANDROID_LOG_ERROR, "Assimp", "Asset manager can not find: %s", pFile);
-		return false;
-		}
-
-	__android_log_print(ANDROID_LOG_ERROR, "Assimp", "Asset exists");
-	if (file)
-		::fclose( file);
-	return true;
+bool AndroidJNIIOSystem::Exists( const char* pFile) const {
+    AAsset* asset = AAssetManager_open(mApkAssetManager, pFile, AASSET_MODE_UNKNOWN);
+    FILE* file = ::fopen( (mApkWorkspacePath + getOsSeparator() + std::string(pFile)).c_str(), "rb");
+	
+    if (!asset && !file) {
+        __android_log_print(ANDROID_LOG_ERROR, "Assimp", "Asset manager can not find: %s", pFile);
+        return false;
+    }
+
+    __android_log_print(ANDROID_LOG_ERROR, "Assimp", "Asset exists");
+    if (file) {
+        ::fclose( file);
+    }
+    
+    return true;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Inits Android extractor
-void AndroidJNIIOSystem::AndroidActivityInit(ANativeActivity* activity)
-{
-	mApkWorkspacePath = activity->internalDataPath;
-	mApkAssetManager = activity->assetManager;
+void AndroidJNIIOSystem::AndroidActivityInit(ANativeActivity* activity) {
+    if (activity == nullptr) {
+        return;
+    }
+    mApkWorkspacePath = activity->internalDataPath;
+    mApkAssetManager = activity->assetManager;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Create the directory for the extracted resource
-static int mkpath(std::string path, mode_t mode)
-{
+static int mkpath(std::string path, mode_t mode) {
     if (mkdir(path.c_str(), mode) == -1) {
         switch(errno) {
             case ENOENT:
@@ -125,82 +130,80 @@ static int mkpath(std::string path, mode_t mode)
 
 // ------------------------------------------------------------------------------------------------
 // Extracts android asset
-bool AndroidJNIIOSystem::AndroidExtractAsset(std::string name)
-{
-	std::string newPath = mApkWorkspacePath + getOsSeparator() + name;
+bool AndroidJNIIOSystem::AndroidExtractAsset(std::string name) {
+    std::string newPath = mApkWorkspacePath + getOsSeparator() + name;
 
-	DefaultIOSystem io;
+    DefaultIOSystem io;
 
-	// Do not extract if extracted already
-	if ( io.Exists(newPath.c_str()) ) {
-		__android_log_print(ANDROID_LOG_DEFAULT, "Assimp", "Asset already extracted");
-		return true;
-	}
-	// Open file
-	AAsset* asset = AAssetManager_open(mApkAssetManager, name.c_str(),
+    // Do not extract if extracted already
+    if ( io.Exists(newPath.c_str()) ) {
+        __android_log_print(ANDROID_LOG_DEFAULT, "Assimp", "Asset already extracted");
+        return true;
+    }
+	
+    // Open file
+    AAsset* asset = AAssetManager_open(mApkAssetManager, name.c_str(),
 			AASSET_MODE_UNKNOWN);
-	std::vector<char> assetContent;
-
-	if (asset != NULL) {
-		// Find size
-		off_t assetSize = AAsset_getLength(asset);
-
-		// Prepare input buffer
-		assetContent.resize(assetSize);
-
-		// Store input buffer
-		AAsset_read(asset, &assetContent[0], assetSize);
-
-		// Close
-		AAsset_close(asset);
-
-		// Prepare directory for output buffer
-		std::string directoryNewPath = newPath;
-		directoryNewPath = dirname(&directoryNewPath[0]);
-
-		if (mkpath(directoryNewPath, S_IRUSR | S_IWUSR | S_IXUSR) == -1) {
-			__android_log_print(ANDROID_LOG_ERROR, "assimp",
-					"Can not create the directory for the output file");
-		}
-
-		// Prepare output buffer
-		std::ofstream assetExtracted(newPath.c_str(),
-				std::ios::out | std::ios::binary);
-		if (!assetExtracted) {
-			__android_log_print(ANDROID_LOG_ERROR, "assimp",
-					"Can not open output file");
-		}
-
-		// Write output buffer into a file
-		assetExtracted.write(&assetContent[0], assetContent.size());
-		assetExtracted.close();
-
-		__android_log_print(ANDROID_LOG_DEFAULT, "Assimp", "Asset extracted");
-	} else {
-		__android_log_print(ANDROID_LOG_ERROR, "assimp", "Asset not found: %s", name.c_str());
-		return false;
-	}
-	return true;
+    std::vector<char> assetContent;
+
+    if (asset != NULL) {
+        // Find size
+        off_t assetSize = AAsset_getLength(asset);
+
+        // Prepare input buffer
+        assetContent.resize(assetSize);
+
+        // Store input buffer
+        AAsset_read(asset, &assetContent[0], assetSize);
+
+        // Close
+        AAsset_close(asset);
+
+        // Prepare directory for output buffer
+        std::string directoryNewPath = newPath;
+        directoryNewPath = dirname(&directoryNewPath[0]);
+
+        if (mkpath(directoryNewPath, S_IRUSR | S_IWUSR | S_IXUSR) == -1) {
+             __android_log_print(ANDROID_LOG_ERROR, "assimp", "Can not create the directory for the output file");
+        }
+
+        // Prepare output buffer
+        std::ofstream assetExtracted(newPath.c_str(), std::ios::out | std::ios::binary);
+        if (!assetExtracted) {
+            __android_log_print(ANDROID_LOG_ERROR, "assimp", "Can not open output file");
+        }
+
+        // Write output buffer into a file
+        assetExtracted.write(&assetContent[0], assetContent.size());
+        assetExtracted.close();
+
+        __android_log_print(ANDROID_LOG_DEFAULT, "Assimp", "Asset extracted");
+    } else {
+        __android_log_print(ANDROID_LOG_ERROR, "assimp", "Asset not found: %s", name.c_str());
+        return false;
+    }
+	
+    return true;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Open a new file with a given path.
-IOStream* AndroidJNIIOSystem::Open( const char* strFile, const char* strMode)
-{
-	ai_assert(NULL != strFile);
-	ai_assert(NULL != strMode);
+IOStream* AndroidJNIIOSystem::Open( const char* strFile, const char* strMode) {
+    ai_assert(nullptr != strFile);
+    ai_assert(nullptr != strMode);
 
-	std::string fullPath(mApkWorkspacePath + getOsSeparator() + std::string(strFile));
-	if	(Exists(strFile))
-		AndroidExtractAsset(std::string(strFile));
-
-	FILE* file = ::fopen( fullPath.c_str(), strMode);
+    std::string fullPath(mApkWorkspacePath + getOsSeparator() + std::string(strFile));
+    if (Exists(strFile)) {
+	AndroidExtractAsset(std::string(strFile));
+    }
 
-	if( NULL == file)
-		return NULL;
+    FILE* file = ::fopen( fullPath.c_str(), strMode);
+    if (nullptr == file) {
+        return nullptr;
+    }
 
-	__android_log_print(ANDROID_LOG_ERROR, "assimp", "AndroidIOSystem: file %s opened", fullPath.c_str());
-	return new DefaultIOStream(file, fullPath);
+    __android_log_print(ANDROID_LOG_ERROR, "assimp", "AndroidIOSystem: file %s opened", fullPath.c_str());
+    return new DefaultIOStream(file, fullPath);
 }
 
 #undef PATHLIMIT

+ 36 - 0
test/unit/Common/utSpatialSort.cpp

@@ -82,3 +82,39 @@ TEST_F(utSpatialSort, findPositionsTest) {
     sSort.FindPositions(vecs[0], 0.01f, indices);
     EXPECT_EQ(1u, indices.size());
 }
+
+TEST_F(utSpatialSort, highlyDisplacedPositionsTest) {
+    // Make a cube of positions, and then query it using the SpatialSort object.
+    constexpr unsigned int verticesPerAxis = 10;
+    constexpr ai_real step = 0.001f;
+    // Note the large constant offset here.
+    constexpr ai_real offset = 5000.0f - (0.5f * verticesPerAxis * step);
+    constexpr unsigned int totalNumPositions = verticesPerAxis * verticesPerAxis * verticesPerAxis;
+    aiVector3D* positions = new aiVector3D[totalNumPositions];
+    for (unsigned int x = 0; x < verticesPerAxis; ++x) {
+        for (unsigned int y = 0; y < verticesPerAxis; ++y) {
+            for (unsigned int z = 0; z < verticesPerAxis; ++z) {
+                const unsigned int index = (x * verticesPerAxis * verticesPerAxis) + (y * verticesPerAxis) + z;
+                positions[index] = aiVector3D(offset + (x * step), offset + (y * step), offset + (z * step));
+            }
+        }
+    }
+
+    SpatialSort sSort;
+    sSort.Fill(positions, totalNumPositions, sizeof(aiVector3D));
+
+    // Enough to find a point and its 6 immediate neighbors, but not any other point.
+    const ai_real epsilon = 1.1f * step;
+    std::vector<unsigned int> indices;
+    // Iterate through the _interior_ points of the cube.
+    for (unsigned int x = 1; x < verticesPerAxis - 1; ++x) {
+        for (unsigned int y = 1; y < verticesPerAxis - 1; ++y) {
+            for (unsigned int z = 1; z < verticesPerAxis - 1; ++z) {
+                const unsigned int index = (x * verticesPerAxis * verticesPerAxis) + (y * verticesPerAxis) + z;
+                sSort.FindPositions(positions[index], epsilon, indices);
+                ASSERT_EQ(7u, indices.size());
+            }
+        }
+    }
+    delete[] positions;
+}

+ 3 - 3
test/unit/utVersion.cpp

@@ -44,16 +44,16 @@ class utVersion : public ::testing::Test {
 };
 
 TEST_F( utVersion, aiGetLegalStringTest ) {
-    const char *lv( aiGetLegalString() );
+    const char *lv = aiGetLegalString();
     EXPECT_NE( lv, nullptr );
     std::string text( lv );
 
-    size_t pos( text.find( std::string( "2021" ) ) );
+    size_t pos = text.find(std::string("2021"));
     EXPECT_NE( pos, std::string::npos );
 }
 
 TEST_F( utVersion, aiGetVersionMinorTest ) {
-    EXPECT_EQ( aiGetVersionMinor(), 0U );
+    EXPECT_EQ( aiGetVersionMinor(), 1U );
 }
 
 TEST_F( utVersion, aiGetVersionMajorTest ) {