Просмотр исходного кода

Various Sonar fixes (#123)

- Added unit test for string tools
- Removed unused parameters for root bounding box for MeshShape
- JPH_PI is no longer a define
Jorrit Rouwe 3 лет назад
Родитель
Сommit
4595f85955

+ 1 - 1
.github/workflows/sonar-cloud.yml

@@ -19,7 +19,7 @@ jobs:
     name: Build
     runs-on: ubuntu-latest
     env:
-      SONAR_SCANNER_VERSION: 4.4.0.2170
+      SONAR_SCANNER_VERSION: 4.7.0.2747
       SONAR_SERVER_URL: "https://sonarcloud.io"
       BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed
       CLANG_VERSION: 12

+ 1 - 1
Jolt/AABBTree/AABBTreeToBuffer.h

@@ -222,7 +222,7 @@ public:
 				else
 				{				
 					// Add triangles
-					node_data->mTriangleStart = tri_ctx.Pack(inVertices, node_data->mNode->mTriangles, node_data->mNodeBoundsMin, node_data->mNodeBoundsMax, mTree, outError);
+					node_data->mTriangleStart = tri_ctx.Pack(inVertices, node_data->mNode->mTriangles, mTree, outError);
 					if (node_data->mTriangleStart == uint(-1))
 						return false;
 				}

+ 2 - 6
Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h

@@ -207,9 +207,7 @@ public:
 		}
 
 		/// Constructor
-		JPH_INLINE explicit			DecodingContext(const Header *inHeader) :
-			mRootBoundsMin(Vec3::sLoadFloat3Unsafe(inHeader->mRootBoundsMin)),
-			mRootBoundsMax(Vec3::sLoadFloat3Unsafe(inHeader->mRootBoundsMax))
+		JPH_INLINE explicit			DecodingContext(const Header *inHeader)
 		{
 			// Start with the root node on the stack
 			mNodeStack[0] = inHeader->mRootProperties;
@@ -258,7 +256,7 @@ public:
 					uint32 triangle_block_id = node_properties & OFFSET_MASK;
 					const void *triangles = sGetTriangleBlockStart(inBufferStart, triangle_block_id);
 
-					ioVisitor.VisitTriangles(inTriangleContext, mRootBoundsMin, mRootBoundsMax, triangles, tri_count, triangle_block_id);
+					ioVisitor.VisitTriangles(inTriangleContext, triangles, tri_count, triangle_block_id);
 				}
 
 				// Check if we're done
@@ -280,8 +278,6 @@ public:
 		}
 
 	private:
-		Vec3						mRootBoundsMin;
-		Vec3						mRootBoundsMax;
 		uint32						mNodeStack[StackSize];
 		int							mTop = 0;
 	};

+ 8 - 13
Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h

@@ -95,11 +95,6 @@ public:
 	class EncodingContext
 	{
 	public:
-		EncodingContext() :
-			mNumTriangles(0)
-		{
-		}
-
 		/// Get an upper bound on the amount of bytes needed to store inTriangleCount triangles
 		uint						GetPessimisticMemoryEstimate(uint inTriangleCount) const
 		{
@@ -109,7 +104,7 @@ public:
 
 		/// Pack the triangles in inContainer to ioBuffer. This stores the mMaterialIndex of a triangle in the 8 bit flags.
 		/// Returns uint(-1) on error.
-		uint						Pack(const VertexList &inVertices, const IndexedTriangleList &inTriangles, Vec3Arg inBoundsMin, Vec3Arg inBoundsMax, ByteBuffer &ioBuffer, string &outError)
+		uint						Pack(const VertexList &inVertices, const IndexedTriangleList &inTriangles, ByteBuffer &ioBuffer, string &outError)
 		{
 			// Determine position of triangles start
 			uint offset = (uint)ioBuffer.size();
@@ -217,7 +212,7 @@ public:
 			(bounds.GetSize() / Vec3::sReplicate(COMPONENT_MASK)).StoreFloat3(&ioHeader->mScale);
 		}
 
-		void						GetStats(string &outTriangleCodecName, float &outVerticesPerTriangle)
+		void						GetStats(string &outTriangleCodecName, float &outVerticesPerTriangle) const
 		{
 			// Store stats
 			outTriangleCodecName = "Indexed8BitPackSOA4";
@@ -227,7 +222,7 @@ public:
 	private:
 		using VertexMap = unordered_map<uint32, uint32>;
 
-		uint						mNumTriangles;
+		uint						mNumTriangles = 0;
 		vector<Float3>				mVertices;				///< Output vertices, sorted according to occurrence
 		VertexMap					mVertexMap;				///< Maps from the original mesh vertex index (inVertices) to the index in our output vertices (mVertices)
 		vector<uint>				mOffsetsToPatch;		///< Offsets to the vertex buffer that need to be patched in once all nodes have been packed
@@ -256,7 +251,7 @@ public:
 		}
 
 	public:
-		JPH_INLINE					DecodingContext(const TriangleHeader *inHeader, const ByteBuffer &inBuffer) :
+		JPH_INLINE explicit			DecodingContext(const TriangleHeader *inHeader) :
 			mOffsetX(Vec4::sReplicate(inHeader->mOffset.x)),
 			mOffsetY(Vec4::sReplicate(inHeader->mOffset.y)),
 			mOffsetZ(Vec4::sReplicate(inHeader->mOffset.z)),
@@ -267,7 +262,7 @@ public:
 		}
 
 		/// Unpacks triangles in the format t1v1,t1v2,t1v3, t2v1,t2v2,t2v3, ...
-		JPH_INLINE void				Unpack(Vec3Arg inBoundsMin, Vec3Arg inBoundsMax, const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles) const
+		JPH_INLINE void				Unpack(const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles) const
 		{
 			JPH_ASSERT(inNumTriangles > 0);
 			const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
@@ -310,7 +305,7 @@ public:
 		}
 
 		/// Tests a ray against the packed triangles
-		JPH_INLINE float			TestRay(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, Vec3Arg inBoundsMin, Vec3Arg inBoundsMax, const void *inTriangleStart, uint32 inNumTriangles, float inClosest, uint32 &outClosestTriangleIndex) const
+		JPH_INLINE float			TestRay(Vec3Arg inRayOrigin, Vec3Arg inRayDirection, const void *inTriangleStart, uint32 inNumTriangles, float inClosest, uint32 &outClosestTriangleIndex) const
 		{
 			JPH_ASSERT(inNumTriangles > 0);
 			const TriangleBlockHeader *header = reinterpret_cast<const TriangleBlockHeader *>(inTriangleStart);
@@ -421,9 +416,9 @@ public:
 		}
 
 		/// Unpacks triangles and flags, convencience function
-		JPH_INLINE void				Unpack(Vec3Arg inBoundsMin, Vec3Arg inBoundsMax, const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles, uint8 *outTriangleFlags) const
+		JPH_INLINE void				Unpack(const void *inTriangleStart, uint32 inNumTriangles, Vec3 *outTriangles, uint8 *outTriangleFlags) const
 		{
-			Unpack(inBoundsMin, inBoundsMax, inTriangleStart, inNumTriangles, outTriangles);
+			Unpack(inTriangleStart, inNumTriangles, outTriangles);
 			sGetFlags(inTriangleStart, inNumTriangles, outTriangleFlags);
 		}
 

+ 9 - 6
Jolt/Core/Result.h

@@ -14,10 +14,9 @@ public:
 						Result()									{ }
 						
 	/// Copy constructor
-						Result(const Result<Type> &inRHS)
+						Result(const Result<Type> &inRHS) :
+		mState(inRHS.mState)
 	{
-		mState = inRHS.mState;
-
 		switch (inRHS.mState)
 		{
 		case EState::Valid:
@@ -29,15 +28,15 @@ public:
 			break;
 
 		case EState::Invalid:
+		default:
 			break;
 		}
 	}
 
 	/// Move constructor
-						Result(Result<Type> &&inRHS) noexcept
+						Result(Result<Type> &&inRHS) noexcept :
+		mState(inRHS.mState)
 	{
-		mState = inRHS.mState;
-
 		switch (inRHS.mState)
 		{
 		case EState::Valid:
@@ -49,6 +48,7 @@ public:
 			break;
 
 		case EState::Invalid:
+		default:
 			break;
 		}
 
@@ -76,6 +76,7 @@ public:
 			break;
 
 		case EState::Invalid:
+		default:
 			break;
 		}
 
@@ -100,6 +101,7 @@ public:
 			break;
 
 		case EState::Invalid:
+		default:
 			break;
 		}
 
@@ -122,6 +124,7 @@ public:
 			break;
 
 		case EState::Invalid:
+		default:
 			break;
 		}
 

+ 6 - 6
Jolt/Core/StringTools.cpp

@@ -23,7 +23,7 @@ string StringFormat(const char *inFMT, ...)
 	return string(buffer);
 }
 
-void StringReplace(string &ioString, string inSearch, string inReplace)
+void StringReplace(string &ioString, const string_view &inSearch, const string_view &inReplace)
 {
 	size_t index = 0;
 	for (;;)
@@ -38,7 +38,7 @@ void StringReplace(string &ioString, string inSearch, string inReplace)
 	}
 }
 
-void StringToVector(const string &inString, vector<string> &outVector, const string &inDelimiter, bool inClearVector)
+void StringToVector(const string &inString, vector<string> &outVector, const string_view &inDelimiter, bool inClearVector)
 {
 	JPH_ASSERT(inDelimiter.size() > 0);
 
@@ -65,23 +65,23 @@ void StringToVector(const string &inString, vector<string> &outVector, const str
 	outVector.push_back(s);
 }
 
-void VectorToString(const vector<string> &inVector, string &outString, const string &inDelimiter)
+void VectorToString(const vector<string> &inVector, string &outString, const string_view &inDelimiter)
 {
 	// Ensure string empty
 	outString.clear();
 
-	for (vector<string>::const_iterator i = inVector.begin(); i != inVector.end(); ++i)
+	for (const string &s : inVector)
 	{
 		// Add delimiter if not first element
 		if (!outString.empty())
 			outString.append(inDelimiter);
 
 		// Add element
-		outString.append(*i);
+		outString.append(s);
 	}
 }
 
-string ToLower(const string &inString)
+string ToLower(const string_view &inString)
 {
 	string out;
 	out.reserve(inString.length());

+ 4 - 4
Jolt/Core/StringTools.h

@@ -31,16 +31,16 @@ constexpr uint64 HashString(const char *inString)
 }
 
 /// Replace substring with other string
-void StringReplace(string &ioString, string inSearch, string inReplace);
+void StringReplace(string &ioString, const string_view &inSearch, const string_view &inReplace);
 
 /// Convert a delimited string to an array of strings
-void StringToVector(const string &inString, vector<string> &outVector, const string &inDelimiter = ",", bool inClearVector = true);
+void StringToVector(const string &inString, vector<string> &outVector, const string_view &inDelimiter = ",", bool inClearVector = true);
 
 /// Convert an array strings to a delimited string
-void VectorToString(const vector<string> &inVector, string &outString, const string &inDelimiter = ",");
+void VectorToString(const vector<string> &inVector, string &outString, const string_view &inDelimiter = ",");
 
 /// Convert a string to lower case
-string ToLower(const string &inString);
+string ToLower(const string_view &inString);
 
 /// Converts the lower 4 bits of inNibble to a string that represents the number in binary format
 const char *NibbleToBinary(uint32 inNibble);

+ 10 - 10
Jolt/Math/Mat44.h

@@ -126,21 +126,21 @@ public:
 	JPH_INLINE Mat44 &			operator += (Mat44Arg inM);
 
 	/// Access to the columns
-	JPH_INLINE const Vec3		GetAxisX() const										{ return Vec3(mCol[0]); }
+	JPH_INLINE Vec3				GetAxisX() const										{ return Vec3(mCol[0]); }
 	JPH_INLINE void				SetAxisX(Vec3Arg inV)									{ mCol[0] = Vec4(inV, 0.0f); }
-	JPH_INLINE const Vec3		GetAxisY() const										{ return Vec3(mCol[1]); }
+	JPH_INLINE Vec3				GetAxisY() const										{ return Vec3(mCol[1]); }
 	JPH_INLINE void				SetAxisY(Vec3Arg inV)									{ mCol[1] = Vec4(inV, 0.0f); }
-	JPH_INLINE const Vec3		GetAxisZ() const										{ return Vec3(mCol[2]); }
+	JPH_INLINE Vec3				GetAxisZ() const										{ return Vec3(mCol[2]); }
 	JPH_INLINE void				SetAxisZ(Vec3Arg inV)									{ mCol[2] = Vec4(inV, 0.0f); }
-	JPH_INLINE const Vec3		GetTranslation() const									{ return Vec3(mCol[3]); }
+	JPH_INLINE Vec3				GetTranslation() const									{ return Vec3(mCol[3]); }
 	JPH_INLINE void				SetTranslation(Vec3Arg inV)								{ mCol[3] = Vec4(inV, 1.0f); }
-	JPH_INLINE const Vec3		GetDiagonal3() const									{ return Vec3(mCol[0][0], mCol[1][1], mCol[2][2]); }
+	JPH_INLINE Vec3				GetDiagonal3() const									{ return Vec3(mCol[0][0], mCol[1][1], mCol[2][2]); }
 	JPH_INLINE void				SetDiagonal3(Vec3Arg inV)								{ mCol[0][0] = inV.GetX(); mCol[1][1] = inV.GetY(); mCol[2][2] = inV.GetZ(); }
-	JPH_INLINE const Vec4		GetDiagonal4() const									{ return Vec4(mCol[0][0], mCol[1][1], mCol[2][2], mCol[3][3]); }
+	JPH_INLINE Vec4				GetDiagonal4() const									{ return Vec4(mCol[0][0], mCol[1][1], mCol[2][2], mCol[3][3]); }
 	JPH_INLINE void				SetDiagonal4(Vec4Arg inV)								{ mCol[0][0] = inV.GetX(); mCol[1][1] = inV.GetY(); mCol[2][2] = inV.GetZ(); mCol[3][3] = inV.GetW(); }
-	JPH_INLINE const Vec3		GetColumn3(uint inCol) const							{ JPH_ASSERT(inCol < 4); return Vec3(mCol[inCol]); }
+	JPH_INLINE Vec3				GetColumn3(uint inCol) const							{ JPH_ASSERT(inCol < 4); return Vec3(mCol[inCol]); }
 	JPH_INLINE void				SetColumn3(uint inCol, Vec3Arg inV)						{ JPH_ASSERT(inCol < 4); mCol[inCol] = Vec4(inV, inCol == 3? 1.0f : 0.0f); }
-	JPH_INLINE const Vec4		GetColumn4(uint inCol) const							{ JPH_ASSERT(inCol < 4); return mCol[inCol]; }
+	JPH_INLINE Vec4				GetColumn4(uint inCol) const							{ JPH_ASSERT(inCol < 4); return mCol[inCol]; }
 	JPH_INLINE void				SetColumn4(uint inCol, Vec4Arg inV)						{ JPH_ASSERT(inCol < 4); mCol[inCol] = inV; }
 
 	/// Store matrix to memory
@@ -177,10 +177,10 @@ public:
 	JPH_INLINE void				SetRotation(Mat44Arg inRotation);
 
 	/// Convert to quaternion
-	JPH_INLINE const Quat		GetQuaternion() const;
+	JPH_INLINE Quat				GetQuaternion() const;
 
 	/// Get matrix that transforms a direction with the same transform as this matrix (length is not preserved)
-	JPH_INLINE const Mat44		GetDirectionPreservingMatrix() const					{ return GetRotation().Inversed3x3().Transposed3x3(); }
+	JPH_INLINE Mat44			GetDirectionPreservingMatrix() const					{ return GetRotation().Inversed3x3().Transposed3x3(); }
 
 	/// Pre multiply by translation matrix: result = this * Mat44::sTranslation(inTranslation)
 	JPH_INLINE Mat44			PreTranslated(Vec3Arg inTranslation) const;

+ 1 - 1
Jolt/Math/Mat44.inl

@@ -940,7 +940,7 @@ Mat44 Mat44::Inversed3x3() const
 #endif
 }
 
-const Quat Mat44::GetQuaternion() const
+Quat Mat44::GetQuaternion() const
 {
 	JPH_ASSERT(mCol[3] == Vec4(0, 0, 0, 1));
 

+ 8 - 8
Jolt/Math/Math.h

@@ -6,16 +6,16 @@
 JPH_NAMESPACE_BEGIN
 
 /// The constant \f$\pi\f$
-#define JPH_PI       3.14159265358979323846f
+static constexpr float JPH_PI = 3.14159265358979323846f;
 
 /// Convert a value from degrees to radians
-inline constexpr float DegreesToRadians(float inV)
+constexpr float DegreesToRadians(float inV)
 {
 	return inV * (JPH_PI / 180.0f);
 }
 
 /// Convert a value from radians to degrees
-inline constexpr float RadiansToDegrees(float inV)
+constexpr float RadiansToDegrees(float inV)
 {
 	return inV * (180.0f / JPH_PI);
 }
@@ -41,35 +41,35 @@ inline float CenterAngleAroundZero(float inV)
 
 /// Clamp a value between two values
 template <typename T>
-inline constexpr T Clamp(T inV, T inMin, T inMax)
+constexpr T Clamp(T inV, T inMin, T inMax)
 {
 	return min(max(inV, inMin), inMax);
 }
 
 /// Square a value
 template <typename T>
-inline constexpr T Square(T inV)
+constexpr T Square(T inV)
 {
 	return inV * inV;
 }
 
 /// Returns \f$inV^3\f$.
 template <typename T>
-inline constexpr T Cubed(T inV)
+constexpr T Cubed(T inV)
 {
 	return inV * inV * inV;
 }
 
 /// Get the sign of a value
 template <typename T>
-inline constexpr T Sign(T inV)
+constexpr T Sign(T inV)
 {
 	return inV < 0? T(-1) : T(1);
 }
 
 /// Check if inV is a power of 2
 template <typename T>
-inline constexpr bool IsPowerOf2(T inV)
+constexpr bool IsPowerOf2(T inV)
 {
 	return (inV & (inV - 1)) == 0;
 }

+ 10 - 10
Jolt/Math/Matrix.h

@@ -28,7 +28,7 @@ public:
 			mCol[c].SetZero();
 	}
 
-	inline static const Matrix				sZero()													{ Matrix m; m.SetZero(); return m; }
+	inline static Matrix					sZero()													{ Matrix m; m.SetZero(); return m; }
 
 	/// Check if this matrix consists of all zeros
 	inline bool								IsZero() const
@@ -51,7 +51,7 @@ public:
 			mCol[rc].mF32[rc] = 1.0f;
 	}
 
-	inline static const Matrix				sIdentity()												{ Matrix m; m.SetIdentity(); return m; }
+	inline static Matrix					sIdentity()												{ Matrix m; m.SetIdentity(); return m; }
 
 	/// Check if this matrix is identity
 	bool									IsIdentity() const										{ return *this == sIdentity(); }
@@ -67,7 +67,7 @@ public:
 			mCol[rc].mF32[rc] = inV[rc];
 	}
 
-	inline static const Matrix				sDiagonal(const Vector<Rows < Cols? Rows : Cols> &inV)	
+	inline static Matrix					sDiagonal(const Vector<Rows < Cols? Rows : Cols> &inV)	
 	{ 
 		Matrix m; 
 		m.SetDiagonal(inV); 
@@ -125,7 +125,7 @@ public:
 
 	/// Multiply matrix by matrix
 	template <uint OtherCols>
-	inline const Matrix<Rows, OtherCols>	operator * (const Matrix<Cols, OtherCols> &inM) const
+	inline Matrix<Rows, OtherCols>	operator * (const Matrix<Cols, OtherCols> &inM) const
 	{
 		Matrix<Rows, OtherCols> m;
 		for (uint c = 0; c < OtherCols; ++c)
@@ -154,7 +154,7 @@ public:
 	}
 
 	/// Multiply matrix with float
-	inline const Matrix						operator * (float inV) const
+	inline Matrix							operator * (float inV) const
 	{
 		Matrix m;
 		for (uint c = 0; c < Cols; ++c)
@@ -162,13 +162,13 @@ public:
 		return m;
 	}
 
-	inline friend const Matrix				operator * (float inV, const Matrix &inM)				
+	inline friend Matrix					operator * (float inV, const Matrix &inM)				
 	{ 
 		return inM * inV; 
 	}
 
 	/// Per element addition of matrix
-	inline const Matrix						operator + (const Matrix &inM) const
+	inline Matrix							operator + (const Matrix &inM) const
 	{
 		Matrix m;
 		for (uint c = 0; c < Cols; ++c)
@@ -177,7 +177,7 @@ public:
 	}
 
 	/// Per element subtraction of matrix
-	inline const Matrix						operator - (const Matrix &inM) const
+	inline Matrix							operator - (const Matrix &inM) const
 	{
 		Matrix m;
 		for (uint c = 0; c < Cols; ++c)
@@ -186,7 +186,7 @@ public:
 	}
 
 	/// Transpose matrix
-	inline const Matrix<Cols, Rows>			Transposed() const
+	inline Matrix<Cols, Rows>				Transposed() const
 	{
 		Matrix<Cols, Rows> m;
 		for (uint r = 0; r < Rows; ++r)
@@ -204,7 +204,7 @@ public:
 		return GaussianElimination(copy, *this);
 	}
 
-	inline const Matrix						Inversed() const
+	inline Matrix							Inversed() const
 	{
 		Matrix m;
 		m.SetInversed(*this);

+ 8 - 8
Jolt/Math/Vector.h

@@ -24,7 +24,7 @@ public:
 			mF32[r] = 0.0f;
 	}
 
-	inline static const Vector	sZero()													{ Vector v; v.SetZero(); return v; }
+	inline static Vector		sZero()													{ Vector v; v.SetZero(); return v; }
 
 	/// Copy a (part) of another vector into this vector
 	template <class OtherVector>
@@ -88,7 +88,7 @@ public:
 	}
 
 	/// Multiply vector with float
-	inline const Vector			operator * (const float inV2) const
+	inline Vector				operator * (const float inV2) const
 	{
 		Vector v;
 		for (uint r = 0; r < Rows; ++r)
@@ -104,13 +104,13 @@ public:
 	}
 
 	/// Multiply vector with float
-	inline friend const Vector	operator * (const float inV1, const Vector &inV2)
+	inline friend Vector		operator * (const float inV1, const Vector &inV2)
 	{
 		return inV2 * inV1;
 	}
 
 	/// Divide vector by float
-	inline const Vector			operator / (float inV2) const
+	inline Vector				operator / (float inV2) const
 	{
 		Vector v;
 		for (uint r = 0; r < Rows; ++r)
@@ -119,7 +119,7 @@ public:
 	}
 
 	/// Add two float vectors (component wise)
-	inline const Vector			operator + (const Vector &inV2) const
+	inline Vector				operator + (const Vector &inV2) const
 	{
 		Vector v;
 		for (uint r = 0; r < Rows; ++r)
@@ -135,7 +135,7 @@ public:
 	}
 
 	/// Negate
-	inline const Vector			operator - () const
+	inline Vector				operator - () const
 	{
 		Vector v;
 		for (uint r = 0; r < Rows; ++r)
@@ -144,7 +144,7 @@ public:
 	}
 
 	/// Subtract two float vectors (component wise)
-	inline const Vector			operator - (const Vector &inV2) const
+	inline Vector				operator - (const Vector &inV2) const
 	{
 		Vector v;
 		for (uint r = 0; r < Rows; ++r)
@@ -187,7 +187,7 @@ public:
 	}
 
 	/// Normalize vector
-	inline const Vector			Normalized() const
+	inline Vector				Normalized() const
 	{
 		return *this / Length();
 	}

+ 2 - 14
Jolt/Physics/Collision/BroadPhase/BroadPhaseLayer.h

@@ -20,13 +20,8 @@ public:
 
 	JPH_INLINE 						BroadPhaseLayer() = default;
 	JPH_INLINE explicit constexpr	BroadPhaseLayer(Type inValue) : mValue(inValue) { }
-	JPH_INLINE constexpr			BroadPhaseLayer(const BroadPhaseLayer &inRHS) : mValue(inRHS.mValue) { }
-
-	JPH_INLINE BroadPhaseLayer &	operator = (const BroadPhaseLayer &inRHS)
-	{
-		mValue = inRHS.mValue;
-		return *this;
-	}
+	JPH_INLINE constexpr			BroadPhaseLayer(const BroadPhaseLayer &) = default;
+	JPH_INLINE BroadPhaseLayer &	operator = (const BroadPhaseLayer &) = default;
 
 	JPH_INLINE constexpr bool		operator == (const BroadPhaseLayer &inRHS) const
 	{
@@ -102,13 +97,6 @@ public:
 	{
 	}
 
-	/// Copy constructor
-									DefaultBroadPhaseLayerFilter(const DefaultBroadPhaseLayerFilter &inRHS) :
-		mObjectVsBroadPhaseLayerFilter(inRHS.mObjectVsBroadPhaseLayerFilter),
-		mLayer(inRHS.mLayer)
-	{
-	}
-
 	// See BroadPhaseLayerFilter::ShouldCollide
 	virtual bool					ShouldCollide(BroadPhaseLayer inLayer) const override
 	{

+ 32 - 52
Jolt/Physics/Collision/BroadPhase/QuadTree.cpp

@@ -127,7 +127,7 @@ void QuadTree::SetBodyLocation(TrackingVector &ioTracking, BodyID inBodyID, uint
 #endif
 }
 
-void QuadTree::InvalidateBodyLocation(TrackingVector &ioTracking, BodyID inBodyID)
+void QuadTree::sInvalidateBodyLocation(TrackingVector &ioTracking, BodyID inBodyID)
 {
 	ioTracking[inBodyID.GetIndex()].mBodyLocation = Tracking::cInvalidBodyLocation;
 }
@@ -138,7 +138,7 @@ QuadTree::~QuadTree()
 	DiscardOldTree();
 
 	// Get the current root node
-	RootNode &root_node = GetCurrentRoot();
+	const RootNode &root_node = GetCurrentRoot();
 
 	// Collect all bodies
 	Allocator::Batch free_batch;
@@ -157,16 +157,13 @@ QuadTree::~QuadTree()
 			const Node &node = mAllocator->Get(node_idx);
 
 			// Recurse and get all child nodes
-			for (int i = 0; i < 4; ++i)
-			{
-				NodeID child_node_id = node.mChildNodeID[i];
+			for (NodeID child_node_id : node.mChildNodeID)
 				if (child_node_id.IsValid() && child_node_id.IsNode())
 				{
 					JPH_ASSERT(top < cStackSize);
 					node_stack[top] = child_node_id;
 					top++;
 				}
-			}
 
 			// Mark node to be freed
 			mAllocator->AddObjectToBatch(free_batch, node_idx);
@@ -230,7 +227,7 @@ void QuadTree::UpdatePrepare(const BodyVector &inBodies, TrackingVector &ioTrack
 	mIsDirty = false;
 
 	// Get the current root node
-	RootNode &root_node = GetCurrentRoot();
+	const RootNode &root_node = GetCurrentRoot();
 
 #ifdef JPH_DUMP_BROADPHASE_TREE
 	DumpTree(root_node.GetNodeID(), StringFormat("%s_PRE", mName).c_str());
@@ -281,16 +278,13 @@ void QuadTree::UpdatePrepare(const BodyVector &inBodies, TrackingVector &ioTrack
 			else
 			{
 				// Node is changed, recurse and get all children
-				for (int i = 0; i < 4; ++i)
-				{
-					NodeID child_node_id = node.mChildNodeID[i];
+				for (NodeID child_node_id : node.mChildNodeID)
 					if (child_node_id.IsValid())
 					{
 						JPH_ASSERT(top < cStackSize);
 						node_stack[top] = child_node_id;
 						top++;
 					}
-				}
 
 				// Mark node to be freed
 				mAllocator->AddObjectToBatch(mFreeNodeBatch, node_idx);
@@ -374,7 +368,7 @@ void QuadTree::sPartition(NodeID *ioNodeIDs, Vec3 *ioNodeCenters, int inNumber,
 	// Calculate bounding box of box centers
 	Vec3 center_min = Vec3::sReplicate(cLargeFloat);
 	Vec3 center_max = Vec3::sReplicate(-cLargeFloat);
-	for (Vec3 *c = ioNodeCenters, *c_end = ioNodeCenters + inNumber; c < c_end; ++c)
+	for (const Vec3 *c = ioNodeCenters, *c_end = ioNodeCenters + inNumber; c < c_end; ++c)
 	{
 		Vec3 center = *c;
 		center_min = Vec3::sMin(center_min, center);
@@ -488,7 +482,7 @@ QuadTree::NodeID QuadTree::BuildTree(const BodyVector &inBodies, TrackingVector
 	Vec3 *centers = new Vec3 [inNumber];
 	JPH_ASSERT(IsAligned(centers, 16));
 	Vec3 *c = centers;
-	for (NodeID *n = ioNodeIDs, *n_end = ioNodeIDs + inNumber; n < n_end; ++n, ++c)
+	for (const NodeID *n = ioNodeIDs, *n_end = ioNodeIDs + inNumber; n < n_end; ++n, ++c)
 		*c = GetNodeOrBodyBounds(inBodies, *n).GetCenter();
 	
 	// The algorithm is a recursive tree build, but to avoid the call overhead we keep track of a stack here
@@ -678,29 +672,25 @@ bool QuadTree::TryInsertLeaf(TrackingVector &ioTracking, int inNodeIndex, NodeID
 
 	// Find an empty child
 	for (uint32 child_idx = 0; child_idx < 4; ++child_idx)
-		if (!node.mChildNodeID[child_idx].IsValid())
+		if (node.mChildNodeID[child_idx].CompareExchange(NodeID::sInvalid(), inLeafID)) // Check if we can claim it
 		{
-			// Check if we can claim it
-			if (node.mChildNodeID[child_idx].CompareExchange(NodeID::sInvalid(), inLeafID))
-			{
-				// We managed to add it to the node
+			// We managed to add it to the node
 
-				// If leaf was a body, we need to update its bookkeeping
-				if (!leaf_is_node)
-					SetBodyLocation(ioTracking, inLeafID.GetBodyID(), inNodeIndex, child_idx);
+			// If leaf was a body, we need to update its bookkeeping
+			if (!leaf_is_node)
+				SetBodyLocation(ioTracking, inLeafID.GetBodyID(), inNodeIndex, child_idx);
 
-				// Now set the bounding box making the child valid for queries
-				node.SetChildBounds(child_idx, inLeafBounds);
+			// Now set the bounding box making the child valid for queries
+			node.SetChildBounds(child_idx, inLeafBounds);
 
-				// Widen the bounds for our parents too
-				WidenAndMarkNodeAndParentsChanged(inNodeIndex, inLeafBounds);
+			// Widen the bounds for our parents too
+			WidenAndMarkNodeAndParentsChanged(inNodeIndex, inLeafBounds);
 
-				// Update body counter
-				mNumBodies += inLeafNumBodies;
+			// Update body counter
+			mNumBodies += inLeafNumBodies;
 
-				// And we're done
-				return true;
-			}
+			// And we're done
+			return true;
 		}
 
 	return false;
@@ -818,23 +808,20 @@ void QuadTree::AddBodiesAbort(TrackingVector &ioTracking, const AddState &inStat
 		if (child_node_id.IsBody())
 		{
 			// Reset location of body
-			InvalidateBodyLocation(ioTracking, child_node_id.GetBodyID());
+			sInvalidateBodyLocation(ioTracking, child_node_id.GetBodyID());
 		}
 		else
 		{
 			// Process normal node
 			uint32 node_idx = child_node_id.GetNodeIndex();
 			const Node &node = mAllocator->Get(node_idx);
-			for (int i = 0; i < 4; ++i)
-			{
-				NodeID sub_child_node_id = node.mChildNodeID[i];
+			for (NodeID sub_child_node_id : node.mChildNodeID)
 				if (sub_child_node_id.IsValid())
 				{
 					JPH_ASSERT(top < cStackSize);
 					node_stack[top] = sub_child_node_id;
 					top++;
 				}
-			}
 
 			// Mark it to be freed
 			mAllocator->AddObjectToBatch(free_batch, node_idx);
@@ -847,7 +834,7 @@ void QuadTree::AddBodiesAbort(TrackingVector &ioTracking, const AddState &inStat
 	mAllocator->DestructObjectBatch(free_batch);
 }
 
-void QuadTree::RemoveBodies([[maybe_unused]] const BodyVector &inBodies, TrackingVector &ioTracking, BodyID *ioBodyIDs, int inNumber)
+void QuadTree::RemoveBodies([[maybe_unused]] const BodyVector &inBodies, TrackingVector &ioTracking, const BodyID *ioBodyIDs, int inNumber)
 {
 	// Assert sane input
 	JPH_ASSERT(ioBodyIDs != nullptr);
@@ -856,7 +843,7 @@ void QuadTree::RemoveBodies([[maybe_unused]] const BodyVector &inBodies, Trackin
 	// Mark tree dirty
 	mIsDirty = true;
 
-	for (BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)
+	for (const BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)
 	{
 		// Check if BodyID is correct
 		JPH_ASSERT(inBodies[cur->GetIndex()]->GetID() == *cur, "Provided BodyID doesn't match BodyID in body manager");
@@ -866,7 +853,7 @@ void QuadTree::RemoveBodies([[maybe_unused]] const BodyVector &inBodies, Trackin
 		GetBodyLocation(ioTracking, *cur, node_idx, child_idx);
 
 		// First we reset our internal bookkeeping
-		InvalidateBodyLocation(ioTracking, *cur);
+		sInvalidateBodyLocation(ioTracking, *cur);
 
 		// Then we make the bounding box invalid, no queries can find this node anymore
 		Node &node = mAllocator->Get(node_idx);
@@ -883,13 +870,13 @@ void QuadTree::RemoveBodies([[maybe_unused]] const BodyVector &inBodies, Trackin
 	mNumBodies -= inNumber;
 }
 
-void QuadTree::NotifyBodiesAABBChanged(const BodyVector &inBodies, const TrackingVector &inTracking, BodyID *ioBodyIDs, int inNumber)
+void QuadTree::NotifyBodiesAABBChanged(const BodyVector &inBodies, const TrackingVector &inTracking, const BodyID *ioBodyIDs, int inNumber)
 {
 	// Assert sane input
 	JPH_ASSERT(ioBodyIDs != nullptr);
 	JPH_ASSERT(inNumber > 0);
 
-	for (BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)
+	for (const BodyID *cur = ioBodyIDs, *end = ioBodyIDs + inNumber; cur < end; ++cur)
 	{
 		// Check if BodyID is correct
 		const Body *body = inBodies[cur->GetIndex()];
@@ -1375,15 +1362,11 @@ void QuadTree::FindCollidingPairs(const BodyVector &inBodies, const BodyID *inAc
 					// Collision between dynamic pairs need to be picked up only once
 					const Body &body2 = *inBodies[b2_id.GetIndex()];
 					if (inObjectLayerPairFilter(body1.GetObjectLayer(), body2.GetObjectLayer())
-						&& Body::sFindCollidingPairsCanCollide(body1, body2))
+						&& Body::sFindCollidingPairsCanCollide(body1, body2)
+						&& bounds1.Overlaps(body2.GetWorldSpaceBounds())) // In the broadphase we widen the bounding box when a body moves, do a final check to see if the bounding boxes actually overlap
 					{
-						// In the broadphase we widen the bounding box when a body moves, do a final
-						// check to see if the bounding boxes actually overlap
-						if (bounds1.Overlaps(body2.GetWorldSpaceBounds()))
-						{
-							// Store potential hit between bodies
-							ioPairCollector.AddHit({ b1_id, b2_id });
-						}
+						// Store potential hit between bodies
+						ioPairCollector.AddHit({ b1_id, b2_id });
 					}
 				}
 			}
@@ -1564,9 +1547,7 @@ void QuadTree::DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) cons
 			f << "node" << node_str << "[label = \"Node " << node_str << "\nVolume: " << ConvertToString(bounds.GetVolume()) << "\" color=" << (node.mIsChanged? "red" : "black") << "]\n";
 
 			// Recurse and get all children
-			for (int i = 0; i < 4; ++i)
-			{
-				NodeID child_node_id = node.mChildNodeID[i];
+			for (NodeID child_node_id : node.mChildNodeID)
 				if (child_node_id.IsValid())
 				{
 					JPH_ASSERT(top < cStackSize);
@@ -1581,7 +1562,6 @@ void QuadTree::DumpTree(const NodeID &inRoot, const char *inFileNamePrefix) cons
 						f << "node" << ConvertToString(child_node_id.GetNodeIndex());
 					f << "\n";
 				}
-			}
 		}
 		--top;
 	} 

+ 4 - 5
Jolt/Physics/Collision/BroadPhase/QuadTree.h

@@ -220,11 +220,10 @@ public:
 	void						AddBodiesAbort(TrackingVector &ioTracking, const AddState &inState);
 
 	/// Remove inNumber bodies in ioBodyIDs from the quadtree.
-	/// ioBodyIDs may be shuffled around by this function.
-	void						RemoveBodies(const BodyVector &inBodies, TrackingVector &ioTracking, BodyID *ioBodyIDs, int inNumber);
+	void						RemoveBodies(const BodyVector &inBodies, TrackingVector &ioTracking, const BodyID *ioBodyIDs, int inNumber);
 
-	/// Call whenever the aabb of a body changes (can change order of ioBodyIDs array).
-	void						NotifyBodiesAABBChanged(const BodyVector &inBodies, const TrackingVector &inTracking, BodyID *ioBodyIDs, int inNumber);
+	/// Call whenever the aabb of a body changes.
+	void						NotifyBodiesAABBChanged(const BodyVector &inBodies, const TrackingVector &inTracking, const BodyID *ioBodyIDs, int inNumber);
 
 	/// Cast a ray and get the intersecting bodies in ioCollector.
 	void						CastRay(const RayCast &inRay, RayCastBodyCollector &ioCollector, const ObjectLayerFilter &inObjectLayerFilter, const TrackingVector &inTracking) const;
@@ -271,7 +270,7 @@ private:
 	/// Caches location of body inBodyID in the tracker, body can be found in mNodes[inNodeIdx].mChildNodeID[inChildIdx]
 	void						GetBodyLocation(const TrackingVector &inTracking, BodyID inBodyID, uint32 &outNodeIdx, uint32 &outChildIdx) const;
 	void						SetBodyLocation(TrackingVector &ioTracking, BodyID inBodyID, uint32 inNodeIdx, uint32 inChildIdx) const;
-	void						InvalidateBodyLocation(TrackingVector &ioTracking, BodyID inBodyID);
+	static void					sInvalidateBodyLocation(TrackingVector &ioTracking, BodyID inBodyID);
 
 	/// Get the current root of the tree
 	JPH_INLINE const RootNode &	GetCurrentRoot() const				{ return mRootNode[mRootNodeIndex]; }

+ 16 - 30
Jolt/Physics/Collision/Shape/CompoundShapeVisitors.h

@@ -21,13 +21,11 @@ struct CompoundShape::CastRayVisitor
 	JPH_INLINE 			CastRayVisitor(const RayCast &inRay, const CompoundShape *inShape, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) : 
 		mRay(inRay),
 		mHit(ioHit),
-		mSubShapeIDCreator(inSubShapeIDCreator)
+		mSubShapeIDCreator(inSubShapeIDCreator),
+		mSubShapeBits(inShape->GetSubShapeIDBits())
 	{ 
 		// Determine ray properties of cast
 		mInvDirection.Set(inRay.mDirection);
-
-		// Determine amount of bits for sub shape
-		mSubShapeBits = inShape->GetSubShapeIDBits();
 	}
 
 	/// Returns true when collision detection should abort because it's not possible to find a better hit
@@ -69,13 +67,11 @@ struct CompoundShape::CastRayVisitorCollector
 		mRay(inRay),
 		mCollector(ioCollector),
 		mSubShapeIDCreator(inSubShapeIDCreator),
+		mSubShapeBits(inShape->GetSubShapeIDBits()),
 		mRayCastSettings(inRayCastSettings)
 	{ 
 		// Determine ray properties of cast
 		mInvDirection.Set(inRay.mDirection);
-
-		// Determine amount of bits for sub shape
-		mSubShapeBits = inShape->GetSubShapeIDBits();
 	}
 
 	/// Returns true when collision detection should abort because it's not possible to find a better hit
@@ -115,10 +111,9 @@ struct CompoundShape::CollidePointVisitor
 	JPH_INLINE			CollidePointVisitor(Vec3Arg inPoint, const CompoundShape *inShape, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector) : 
 		mPoint(inPoint),
 		mSubShapeIDCreator(inSubShapeIDCreator),
-		mCollector(ioCollector) 
+		mCollector(ioCollector),
+		mSubShapeBits(inShape->GetSubShapeIDBits())
 	{ 
-		// Determine amount of bits for sub shape
-		mSubShapeBits = inShape->GetSubShapeIDBits();
 	}
 
 	/// Returns true when collision detection should abort because it's not possible to find a better hit
@@ -153,6 +148,8 @@ struct CompoundShape::CollidePointVisitor
 struct CompoundShape::CastShapeVisitor
 {
 	JPH_INLINE			CastShapeVisitor(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const CompoundShape *inShape, Vec3Arg inScale, const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector) : 
+		mBoxCenter(inShapeCast.mShapeWorldBounds.GetCenter()),
+		mBoxExtent(inShapeCast.mShapeWorldBounds.GetExtent()),
 		mScale(inScale),
 		mShapeCast(inShapeCast),
 		mShapeCastSettings(inShapeCastSettings),
@@ -160,17 +157,11 @@ struct CompoundShape::CastShapeVisitor
 		mCollector(ioCollector),
 		mCenterOfMassTransform2(inCenterOfMassTransform2),
 		mSubShapeIDCreator1(inSubShapeIDCreator1),
-		mSubShapeIDCreator2(inSubShapeIDCreator2)
+		mSubShapeIDCreator2(inSubShapeIDCreator2),
+		mSubShapeBits(inShape->GetSubShapeIDBits())
 	{ 
 		// Determine ray properties of cast
 		mInvDirection.Set(inShapeCast.mDirection);
-
-		// Determine bounding box of cast shape
-		mBoxCenter = inShapeCast.mShapeWorldBounds.GetCenter();
-		mBoxExtent = inShapeCast.mShapeWorldBounds.GetExtent();
-
-		// Determine amount of bits for sub shape
-		mSubShapeBits = inShape->GetSubShapeIDBits();
 	}
 
 	/// Returns true when collision detection should abort because it's not possible to find a better hit
@@ -236,10 +227,9 @@ struct CompoundShape::CollectTransformedShapesVisitor
 		mRotation(inRotation),
 		mScale(inScale),
 		mSubShapeIDCreator(inSubShapeIDCreator),
-		mCollector(ioCollector)
+		mCollector(ioCollector),
+		mSubShapeBits(inShape->GetSubShapeIDBits())
 	{
-		// Determine amount of bits for sub shape
-		mSubShapeBits = inShape->GetSubShapeIDBits();			
 	}
 
 	/// Returns true when collision detection should abort because it's not possible to find a better hit
@@ -296,16 +286,14 @@ struct CompoundShape::CollideCompoundVsShapeVisitor
 		mTransform1(inCenterOfMassTransform1),
 		mTransform2(inCenterOfMassTransform2),
 		mSubShapeIDCreator1(inSubShapeIDCreator1),
-		mSubShapeIDCreator2(inSubShapeIDCreator2)			
+		mSubShapeIDCreator2(inSubShapeIDCreator2),
+		mSubShapeBits(inShape1->GetSubShapeIDBits())
 	{
 		// Get transform from shape 2 to shape 1
 		Mat44 transform2_to_1 = inCenterOfMassTransform1.InversedRotationTranslation() * inCenterOfMassTransform2;
 
 		// Convert bounding box of 2 into space of 1
 		mBoundsOf2InSpaceOf1 = inShape2->GetLocalBounds().Scaled(inScale2).Transformed(transform2_to_1);	
-
-		// Determine amount of bits for sub shape
-		mSubShapeBits = inShape1->GetSubShapeIDBits();
 	}
 
 	/// Returns true when collision detection should abort because it's not possible to find a better hit
@@ -361,7 +349,8 @@ struct CompoundShape::CollideShapeVsCompoundVisitor
 		mTransform1(inCenterOfMassTransform1),
 		mTransform2(inCenterOfMassTransform2),
 		mSubShapeIDCreator1(inSubShapeIDCreator1),
-		mSubShapeIDCreator2(inSubShapeIDCreator2)			
+		mSubShapeIDCreator2(inSubShapeIDCreator2),
+		mSubShapeBits(inShape2->GetSubShapeIDBits())
 	{
 		// Get transform from shape 1 to shape 2
 		Mat44 transform1_to_2 = inCenterOfMassTransform2.InversedRotationTranslation() * inCenterOfMassTransform1;
@@ -369,9 +358,6 @@ struct CompoundShape::CollideShapeVsCompoundVisitor
 		// Convert bounding box of 1 into space of 2
 		mBoundsOf1InSpaceOf2 = inShape1->GetLocalBounds().Scaled(inScale1).Transformed(transform1_to_2);
 		mBoundsOf1InSpaceOf2.ExpandBy(Vec3::sReplicate(inCollideShapeSettings.mMaxSeparationDistance));
-
-		// Determine amount of bits for sub shape
-		mSubShapeBits = inShape2->GetSubShapeIDBits();
 	}
 
 	/// Returns true when collision detection should abort because it's not possible to find a better hit
@@ -440,7 +426,7 @@ struct CompoundShape::GetIntersectingSubShapesVisitor
 	}
 
 	/// Records a hit
-	JPH_INLINE void		VisitShape(const SubShape &inSubShape, uint32 inSubShapeIndex)
+	JPH_INLINE void		VisitShape([[maybe_unused]] const SubShape &inSubShape, uint32 inSubShapeIndex)
 	{
 		JPH_ASSERT(mNumResults < mMaxSubShapeIndices);
 		*mSubShapeIndices++ = inSubShapeIndex;

+ 14 - 14
Jolt/Physics/Collision/Shape/MeshShape.cpp

@@ -362,7 +362,7 @@ Vec3 MeshShape::GetSurfaceNormal(const SubShapeID &inSubShapeID, Vec3Arg inLocal
 
 	// Decode triangle
 	Vec3 v1, v2, v3;
-	const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree), mTree);
+	const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree));
 	triangle_ctx.GetTriangle(block_start, triangle_idx, v1, v2, v3);
 
 	//  Calculate normal
@@ -386,7 +386,7 @@ JPH_INLINE void MeshShape::WalkTree(Visitor &ioVisitor) const
 	const NodeCodec::Header *header = sGetNodeHeader(mTree);
 	NodeCodec::DecodingContext node_ctx(header);
 
-	const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree), mTree);
+	const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree));
 	const uint8 *buffer_start = &mTree[0];
 	node_ctx.WalkTree(buffer_start, triangle_ctx, ioVisitor);
 }
@@ -418,7 +418,7 @@ JPH_INLINE void MeshShape::WalkTreePerTriangle(const SubShapeIDCreator &inSubSha
 			return mVisitor.VisitNodes(inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ, ioProperties, inStackTop);
 		}
 
-		JPH_INLINE void		VisitTriangles(const TriangleCodec::DecodingContext &ioContext, Vec3Arg inRootBoundsMin, Vec3Arg inRootBoundsMax, const void *inTriangles, int inNumTriangles, uint32 inTriangleBlockID) 
+		JPH_INLINE void		VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, uint32 inTriangleBlockID) 
 		{
 			// Create ID for triangle block
 			SubShapeIDCreator block_sub_shape_id = mSubShapeIDCreator2.PushID(inTriangleBlockID, mTriangleBlockIDBits);
@@ -427,7 +427,7 @@ JPH_INLINE void MeshShape::WalkTreePerTriangle(const SubShapeIDCreator &inSubSha
 			JPH_ASSERT(inNumTriangles <= MaxTrianglesPerLeaf);
 			Vec3 vertices[MaxTrianglesPerLeaf * 3];
 			uint8 flags[MaxTrianglesPerLeaf];
-			ioContext.Unpack(inRootBoundsMin, inRootBoundsMax, inTriangles, inNumTriangles, vertices, flags);
+			ioContext.Unpack(inTriangles, inNumTriangles, vertices, flags);
 
 			int triangle_idx = 0;
 			for (const Vec3 *v = vertices, *v_end = vertices + inNumTriangles * 3; v < v_end; v += 3, triangle_idx++)
@@ -486,11 +486,11 @@ void MeshShape::Draw(DebugRenderer *inRenderer, Mat44Arg inCenterOfMassTransform
 				return CountAndSortTrues(valid, ioProperties);
 			}
 
-			JPH_INLINE void		VisitTriangles(const TriangleCodec::DecodingContext &ioContext, Vec3Arg inRootBoundsMin, Vec3Arg inRootBoundsMax, const void *inTriangles, int inNumTriangles, [[maybe_unused]] uint32 inTriangleBlockID) 
+			JPH_INLINE void		VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, [[maybe_unused]] uint32 inTriangleBlockID) 
 			{
 				JPH_ASSERT(inNumTriangles <= MaxTrianglesPerLeaf);
 				Vec3 vertices[MaxTrianglesPerLeaf * 3];
-				ioContext.Unpack(inRootBoundsMin, inRootBoundsMax, inTriangles, inNumTriangles, vertices);
+				ioContext.Unpack(inTriangles, inNumTriangles, vertices);
 
 				if (mDrawTriangleGroups || !mUseMaterialColors || mMaterials.empty())
 				{
@@ -559,13 +559,13 @@ void MeshShape::Draw(DebugRenderer *inRenderer, Mat44Arg inCenterOfMassTransform
 				return CountAndSortTrues(valid, ioProperties);
 			}
 
-			JPH_INLINE void		VisitTriangles(const TriangleCodec::DecodingContext &ioContext, Vec3Arg inRootBoundsMin, Vec3Arg inRootBoundsMax, const void *inTriangles, int inNumTriangles, uint32 inTriangleBlockID) 
+			JPH_INLINE void		VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, uint32 inTriangleBlockID) 
 			{
 				// Decode vertices and flags
 				JPH_ASSERT(inNumTriangles <= MaxTrianglesPerLeaf);
 				Vec3 vertices[MaxTrianglesPerLeaf * 3];
 				uint8 flags[MaxTrianglesPerLeaf];
-				ioContext.Unpack(inRootBoundsMin, inRootBoundsMax, inTriangles, inNumTriangles, vertices, flags);
+				ioContext.Unpack(inTriangles, inNumTriangles, vertices, flags);
 
 				// Loop through triangles
 				const uint8 *f = flags;
@@ -626,11 +626,11 @@ bool MeshShape::CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShap
 			return SortReverseAndStore(distance, mHit.mFraction, ioProperties, &mDistanceStack[inStackTop]);
 		}
 
-		JPH_INLINE void		VisitTriangles(const TriangleCodec::DecodingContext &ioContext, Vec3Arg inRootBoundsMin, Vec3Arg inRootBoundsMax, const void *inTriangles, int inNumTriangles, uint32 inTriangleBlockID) 
+		JPH_INLINE void		VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, uint32 inTriangleBlockID) 
 		{
 			// Test against triangles
 			uint32 triangle_idx;
-			float fraction = ioContext.TestRay(mRayOrigin, mRayDirection, inRootBoundsMin, inRootBoundsMax, inTriangles, inNumTriangles, mHit.mFraction, triangle_idx);
+			float fraction = ioContext.TestRay(mRayOrigin, mRayDirection, inTriangles, inNumTriangles, mHit.mFraction, triangle_idx);
 			if (fraction < mHit.mFraction)
 			{
 				mHit.mFraction = fraction;
@@ -899,7 +899,7 @@ struct MeshShape::MSGetTrianglesContext
 		return CountAndSortTrues(collides, ioProperties);
 	}
 
-	JPH_INLINE void	VisitTriangles(const TriangleCodec::DecodingContext &ioContext, Vec3Arg inRootBoundsMin, Vec3Arg inRootBoundsMax, const void *inTriangles, int inNumTriangles, [[maybe_unused]] uint32 inTriangleBlockID) 
+	JPH_INLINE void	VisitTriangles(const TriangleCodec::DecodingContext &ioContext, const void *inTriangles, int inNumTriangles, [[maybe_unused]] uint32 inTriangleBlockID) 
 	{
 		// When the buffer is full and we cannot process the triangles, abort the tree walk. The next time GetTrianglesNext is called we will continue here.
 		if (mNumTrianglesFound + inNumTriangles > mMaxTrianglesRequested)
@@ -911,7 +911,7 @@ struct MeshShape::MSGetTrianglesContext
 		// Decode vertices
 		JPH_ASSERT(inNumTriangles <= MaxTrianglesPerLeaf);
 		Vec3 vertices[MaxTrianglesPerLeaf * 3];
-		ioContext.Unpack(inRootBoundsMin, inRootBoundsMax, inTriangles, inNumTriangles, vertices);
+		ioContext.Unpack(inTriangles, inNumTriangles, vertices);
 
 		// Store vertices as Float3
 		if (mIsInsideOut)
@@ -995,7 +995,7 @@ int MeshShape::GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTriangl
 	context.mNumTrianglesFound = 0;
 	
 	// Continue (or start) walking the tree
-	const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree), mTree);
+	const TriangleCodec::DecodingContext triangle_ctx(sGetTriangleHeader(mTree));
 	const uint8 *buffer_start = &mTree[0];
 	context.mDecodeCtx.WalkTree(buffer_start, triangle_ctx, context);
 	return context.mNumTrianglesFound;
@@ -1137,7 +1137,7 @@ Shape::Stats MeshShape::GetStats() const
 			return CountAndSortTrues(valid, ioProperties);
 		}
 
-		JPH_INLINE void		VisitTriangles([[maybe_unused]] const TriangleCodec::DecodingContext &ioContext, [[maybe_unused]] Vec3Arg inRootBoundsMin, [[maybe_unused]] Vec3Arg inRootBoundsMax, [[maybe_unused]] const void *inTriangles, int inNumTriangles, [[maybe_unused]] uint32 inTriangleBlockID) 
+		JPH_INLINE void		VisitTriangles([[maybe_unused]] const TriangleCodec::DecodingContext &ioContext, [[maybe_unused]] const void *inTriangles, int inNumTriangles, [[maybe_unused]] uint32 inTriangleBlockID) 
 		{
 			mNumTriangles += inNumTriangles;
 		}

+ 9 - 9
Jolt/Physics/Collision/Shape/StaticCompoundShape.cpp

@@ -90,7 +90,7 @@ void StaticCompoundShape::sPartition(uint *ioBodyIdx, AABox *ioBounds, int inNum
 	// Calculate bounding box of box centers
 	Vec3 center_min = Vec3::sReplicate(FLT_MAX);
 	Vec3 center_max = Vec3::sReplicate(-FLT_MAX);
-	for (AABox *b = ioBounds, *b_end = ioBounds + inNumber; b < b_end; ++b)
+	for (const AABox *b = ioBounds, *b_end = ioBounds + inNumber; b < b_end; ++b)
 	{
 		Vec3 center = b->GetCenter();
 		center_min = Vec3::sMin(center_min, center);
@@ -472,12 +472,12 @@ void StaticCompoundShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator
 	{
 		using CollidePointVisitor::CollidePointVisitor;
 
-		JPH_INLINE bool		ShouldVisitNode(int inStackTop) const
+		JPH_INLINE bool		ShouldVisitNode([[maybe_unused]] int inStackTop) const
 		{
 			return true;
 		}
 
-		JPH_INLINE int		VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop) 
+		JPH_INLINE int		VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
 		{
 			// Test if point overlaps with box
 			UVec4 collides = TestBounds(inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
@@ -529,12 +529,12 @@ void StaticCompoundShape::CollectTransformedShapes(const AABox &inBox, Vec3Arg i
 	{
 		using CollectTransformedShapesVisitor::CollectTransformedShapesVisitor;
 
-		JPH_INLINE bool		ShouldVisitNode(int inStackTop) const
+		JPH_INLINE bool		ShouldVisitNode([[maybe_unused]] int inStackTop) const
 		{
 			return true;
 		}
 
-		JPH_INLINE int		VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop) 
+		JPH_INLINE int		VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
 		{
 			// Test which nodes collide
 			UVec4 collides = TestBounds(inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
@@ -575,12 +575,12 @@ void StaticCompoundShape::sCollideCompoundVsShape(const Shape *inShape1, const S
 	{
 		using CollideCompoundVsShapeVisitor::CollideCompoundVsShapeVisitor;
 
-		JPH_INLINE bool		ShouldVisitNode(int inStackTop) const
+		JPH_INLINE bool		ShouldVisitNode([[maybe_unused]] int inStackTop) const
 		{
 			return true;
 		}
 
-		JPH_INLINE int		VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop) 
+		JPH_INLINE int		VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
 		{
 			// Test which nodes collide
 			UVec4 collides = TestBounds(inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);
@@ -600,12 +600,12 @@ void StaticCompoundShape::sCollideShapeVsCompound(const Shape *inShape1, const S
 	{
 		using CollideShapeVsCompoundVisitor::CollideShapeVsCompoundVisitor;
 
-		JPH_INLINE bool		ShouldVisitNode(int inStackTop) const
+		JPH_INLINE bool		ShouldVisitNode([[maybe_unused]] int inStackTop) const
 		{
 			return true;
 		}
 
-		JPH_INLINE int		VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, int inStackTop) 
+		JPH_INLINE int		VisitNodes(Vec4Arg inBoundsMinX, Vec4Arg inBoundsMinY, Vec4Arg inBoundsMinZ, Vec4Arg inBoundsMaxX, Vec4Arg inBoundsMaxY, Vec4Arg inBoundsMaxZ, UVec4 &ioProperties, [[maybe_unused]] int inStackTop) const
 		{
 			// Test which nodes collide
 			UVec4 collides = TestBounds(inBoundsMinX, inBoundsMinY, inBoundsMinZ, inBoundsMaxX, inBoundsMaxY, inBoundsMaxZ);

+ 20 - 15
Jolt/Physics/Constraints/SixDOFConstraint.cpp

@@ -329,6 +329,7 @@ void SixDOFConstraint::SetupVelocityConstraint(float inDeltaTime)
 			switch (mMotorState[i])
 			{
 			case EMotorState::Off:
+			default:
 				if (HasFriction(axis))
 					mMotorTranslationConstraintPart[i].CalculateConstraintProperties(inDeltaTime, *mBody1, r1_plus_u, *mBody2, r2, translation_axis);
 				else
@@ -432,6 +433,7 @@ void SixDOFConstraint::SetupVelocityConstraint(float inDeltaTime)
 				break;
 
 			case 0b111:
+			default: // All motors off is handled here but the results are unused
 				// Keep entire rotation
 				projected_diff = diff;
 				break;
@@ -453,6 +455,7 @@ void SixDOFConstraint::SetupVelocityConstraint(float inDeltaTime)
 				switch (mMotorState[axis])
 				{
 				case EMotorState::Off:
+				default:
 					if (HasFriction(axis))
 						mMotorRotationConstraintPart[i].CalculateConstraintProperties(inDeltaTime, *mBody1, *mBody2, rotation_axis);
 					else
@@ -482,9 +485,9 @@ void SixDOFConstraint::WarmStartVelocityConstraint(float inWarmStartImpulseRatio
 
 	// Warm start rotation motors
 	if (mRotationMotorActive)
-		for (int i = 0; i < 3; ++i)
-			if (mMotorRotationConstraintPart[i].IsActive())
-				mMotorRotationConstraintPart[i].WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
+		for (AngleConstraintPart &c : mMotorRotationConstraintPart)
+			if (c.IsActive())
+				c.WarmStart(*mBody1, *mBody2, inWarmStartImpulseRatio);
 
 	// Warm start rotation constraints
 	if (IsRotationFullyConstrained())
@@ -512,6 +515,7 @@ bool SixDOFConstraint::SolveVelocityConstraint(float inDeltaTime)
 				switch (mMotorState[i])
 				{
 				case EMotorState::Off:
+				default:
 				{
 					// Apply friction only
 					float max_lambda = mMaxFriction[i] * inDeltaTime;
@@ -535,6 +539,7 @@ bool SixDOFConstraint::SolveVelocityConstraint(float inDeltaTime)
 				switch (mMotorState[axis])
 				{
 				case EMotorState::Off:
+				default:
 				{
 					// Apply friction only
 					float max_lambda = mMaxFriction[axis] * inDeltaTime;
@@ -715,15 +720,15 @@ void SixDOFConstraint::SaveState(StateRecorder &inStream) const
 {
 	TwoBodyConstraint::SaveState(inStream);
 
-	for (int i = 0; i < 3; ++i)
-		mTranslationConstraintPart[i].SaveState(inStream);
+	for (const AxisConstraintPart &c : mTranslationConstraintPart)
+		c.SaveState(inStream);
 	mPointConstraintPart.SaveState(inStream);
 	mSwingTwistConstraintPart.SaveState(inStream);
 	mRotationConstraintPart.SaveState(inStream);
-	for (int i = 0; i < 3; ++i)
-		mMotorTranslationConstraintPart[i].SaveState(inStream);
-	for (int i = 0; i < 3; ++i)
-		mMotorRotationConstraintPart[i].SaveState(inStream);
+	for (const AxisConstraintPart &c : mMotorTranslationConstraintPart)
+		c.SaveState(inStream);
+	for (const AngleConstraintPart &c : mMotorRotationConstraintPart)
+		c.SaveState(inStream);
 
 	inStream.Write(mMotorState);
 	inStream.Write(mTargetVelocity);
@@ -736,15 +741,15 @@ void SixDOFConstraint::RestoreState(StateRecorder &inStream)
 {
 	TwoBodyConstraint::RestoreState(inStream);
 
-	for (int i = 0; i < 3; ++i)
-		mTranslationConstraintPart[i].RestoreState(inStream);
+	for (AxisConstraintPart &c : mTranslationConstraintPart)
+		c.RestoreState(inStream);
 	mPointConstraintPart.RestoreState(inStream);
 	mSwingTwistConstraintPart.RestoreState(inStream);
 	mRotationConstraintPart.RestoreState(inStream);
-	for (int i = 0; i < 3; ++i)
-		mMotorTranslationConstraintPart[i].RestoreState(inStream);
-	for (int i = 0; i < 3; ++i)
-		mMotorRotationConstraintPart[i].RestoreState(inStream);
+	for (AxisConstraintPart &c : mMotorTranslationConstraintPart)
+		c.RestoreState(inStream);
+	for (AngleConstraintPart &c : mMotorRotationConstraintPart)
+		c.RestoreState(inStream);
 
 	inStream.Read(mMotorState);
 	inStream.Read(mTargetVelocity);

+ 1 - 1
Jolt/Skeleton/Skeleton.cpp

@@ -21,7 +21,7 @@ JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(Skeleton)
 	JPH_ADD_ATTRIBUTE(Skeleton, mJoints)
 }
 
-int Skeleton::GetJointIndex(const string &inName) const
+int Skeleton::GetJointIndex(const string_view &inName) const
 {
 	for (int i = 0; i < (int)mJoints.size(); ++i)
 		if (mJoints[i].mName == inName)

+ 1 - 1
Jolt/Skeleton/Skeleton.h

@@ -45,7 +45,7 @@ public:
 	///@}
 
 	/// Find joint by name
-	int						GetJointIndex(const string &inName) const;
+	int						GetJointIndex(const string_view &inName) const;
 
 	/// Fill in parent joint indices based on name
 	void					CalculateParentJointIndices();

+ 6 - 6
Samples/Tests/Character/CharacterBaseTest.h

@@ -46,12 +46,12 @@ protected:
 	void					DrawCharacterState(const CharacterBase *inCharacter, Mat44Arg inCharacterTransform, Vec3Arg inCharacterVelocity);
 
 	// Character size
-	inline static constexpr float cCharacterHeightStanding = 1.35f;
-	inline static constexpr float cCharacterRadiusStanding = 0.3f;
-	inline static constexpr float cCharacterHeightCrouching = 0.8f;
-	inline static constexpr float cCharacterRadiusCrouching = 0.3f;
-	inline static constexpr float cCharacterSpeed = 6.0f;
-	inline static constexpr float cJumpSpeed = 4.0f;
+	static constexpr float	cCharacterHeightStanding = 1.35f;
+	static constexpr float	cCharacterRadiusStanding = 0.3f;
+	static constexpr float	cCharacterHeightCrouching = 0.8f;
+	static constexpr float	cCharacterRadiusCrouching = 0.3f;
+	static constexpr float	cCharacterSpeed = 6.0f;
+	static constexpr float	cJumpSpeed = 4.0f;
 
 	// The different stances for the character
 	RefConst<Shape>			mStandingShape;

+ 97 - 0
UnitTests/Core/StringToolsTest.cpp

@@ -0,0 +1,97 @@
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#include "UnitTestFramework.h"
+#include <Jolt/Core/StringTools.h>
+
+TEST_SUITE("StringToolsTest")
+{
+	TEST_CASE("TestStringFormat")
+	{
+		CHECK(StringFormat("Test: %d", 1234) == "Test: 1234");
+	}
+
+	TEST_CASE("TestConvertToString")
+	{
+		CHECK(ConvertToString(1234) == "1234");
+		CHECK(ConvertToString(-1) == "-1");
+		CHECK(ConvertToString(0x7fffffffffffffffUL) == "9223372036854775807");
+	}
+
+	TEST_CASE("TestStringHash")
+	{
+		CHECK(HashString("This is a test") == 2733878766136413408UL);
+	}
+
+	TEST_CASE("StringReplace")
+	{
+		string value = "Hello this si si a test";
+		StringReplace(value, "si", "is");
+		CHECK(value == "Hello this is is a test");
+		StringReplace(value, "is is", "is");
+		CHECK(value == "Hello this is a test");
+		StringReplace(value, "Hello", "Bye");
+		CHECK(value == "Bye this is a test");
+		StringReplace(value, "a test", "complete");
+		CHECK(value == "Bye this is complete");
+	}
+
+	TEST_CASE("StringToVector")
+	{
+		vector<string> value;
+		StringToVector("", value);
+		CHECK(value.empty());
+
+		StringToVector("a,b,c", value);
+		CHECK(value[0] == "a");
+		CHECK(value[1] == "b");
+		CHECK(value[2] == "c");
+
+		StringToVector("a,.b,.c,", value, ".");
+		CHECK(value[0] == "a,");
+		CHECK(value[1] == "b,");
+		CHECK(value[2] == "c,");
+	}
+
+	TEST_CASE("VectorToString")
+	{
+		vector<string> input;
+		string value;
+		VectorToString(input, value);
+		CHECK(value.empty());
+
+		input = { "a", "b", "c" };
+		VectorToString(input, value);
+		CHECK(value == "a,b,c");
+
+		VectorToString(input, value, ", ");
+		CHECK(value == "a, b, c");
+	}
+
+	TEST_CASE("ToLower")
+	{
+		CHECK(ToLower("123 HeLlO!") == "123 hello!");
+	}
+
+	TEST_CASE("NibbleToBinary")
+	{
+		CHECK(strcmp(NibbleToBinary(0b0000), "0000") == 0);
+		CHECK(strcmp(NibbleToBinary(0b0001), "0001") == 0);
+		CHECK(strcmp(NibbleToBinary(0b0010), "0010") == 0);
+		CHECK(strcmp(NibbleToBinary(0b0011), "0011") == 0);
+		CHECK(strcmp(NibbleToBinary(0b0100), "0100") == 0);
+		CHECK(strcmp(NibbleToBinary(0b0101), "0101") == 0);
+		CHECK(strcmp(NibbleToBinary(0b0110), "0110") == 0);
+		CHECK(strcmp(NibbleToBinary(0b0111), "0111") == 0);
+		CHECK(strcmp(NibbleToBinary(0b1000), "1000") == 0);
+		CHECK(strcmp(NibbleToBinary(0b1001), "1001") == 0);
+		CHECK(strcmp(NibbleToBinary(0b1010), "1010") == 0);
+		CHECK(strcmp(NibbleToBinary(0b1011), "1011") == 0);
+		CHECK(strcmp(NibbleToBinary(0b1100), "1100") == 0);
+		CHECK(strcmp(NibbleToBinary(0b1101), "1101") == 0);
+		CHECK(strcmp(NibbleToBinary(0b1110), "1110") == 0);
+		CHECK(strcmp(NibbleToBinary(0b1111), "1111") == 0);
+
+		CHECK(strcmp(NibbleToBinary(0xfffffff0), "0000") == 0);
+	}
+}

+ 1 - 0
UnitTests/UnitTests.cmake

@@ -6,6 +6,7 @@ set(UNIT_TESTS_SRC_FILES
 	${UNIT_TESTS_ROOT}/Core/FPFlushDenormalsTest.cpp
 	${UNIT_TESTS_ROOT}/Core/JobSystemTest.cpp
 	${UNIT_TESTS_ROOT}/Core/LinearCurveTest.cpp
+	${UNIT_TESTS_ROOT}/Core/StringToolsTest.cpp
 	${UNIT_TESTS_ROOT}/doctest.h
 	${UNIT_TESTS_ROOT}/Geometry/ConvexHullBuilderTest.cpp
 	${UNIT_TESTS_ROOT}/Geometry/EllipseTest.cpp