Repository: godotengine/godot (language: cpp)

Issue:
Godot (Jolt) cannot be cross-compiled on Linux for ppc64 and rv64
### Tested versions

- Master branch as of (44dfa7e710572836cc9b2d88a90b2bb500a2502e)

### System information

Godot v4.4.dev (9ee41ad73) - Windows 10.0.26100 - Multi-window, 2 monitors - Vulkan (Forward+) - dedicated Radeon RX 580 Series (Advanced Micro Devices, Inc.; 31.0.21921.1000) - Intel(R) Core(TM) i5-9600K CPU @ 3.70GHz (6 threads)

### Issue description

Since the merger of Jolt Physics (#99895), now Godot cannot be compiled nor cross-compiled for other architectures (PPC and RISC-V/RV64). It can only be compiled/cross-compiled for x86 and/or ARM.

For both, _it does_ say that the architecture is unsupported:
```bash
In file included from thirdparty/jolt_physics/Jolt/RegisterTypes.cpp:5:
In file included from thirdparty/jolt_physics/Jolt/Jolt.h:8:
thirdparty/jolt_physics/Jolt/Core/Core.h:206:3: error: Unsupported CPU architecture
  206 |         #error Unsupported CPU architecture
      |          ^
BUILD: Elapsed time [00:02:34]
Compiling thirdparty/jolt_physics/Jolt/AABBTree/AABBTreeBuilder.cpp ...
In file included from thirdparty/jolt_physics/Jolt/AABBTree/AABBTreeBuilder.cpp:5:
In file included from thirdparty/jolt_physics/Jolt/Jolt.h:8:
thirdparty/jolt_physics/Jolt/Core/Core.h:206:3: error: Unsupported CPU architecture
  206 |         #error Unsupported CPU architecture
      |          ^
thirdparty/jolt_physics/Jolt/Core/Core.h:458:34: error: use of undeclared identifier 'JPH_CPU_ADDRESS_BITS'
  458 | static_assert(sizeof(void *) == (JPH_CPU_ADDRESS_BITS == 64? 8 : 4), "Invalid size of pointer" );
      |                                  ^
In file included from thirdparty/jolt_physics/Jolt/RegisterTypes.cpp:5:
In file included from thirdparty/jolt_physics/Jolt/Jolt.h:12:
In file included from thirdparty/jolt_physics/Jolt/Core/Array.h:7:
thirdparty/jolt_physics/Jolt/Core/STLAllocator.h:47:63: error: use of undeclared identifier 'JPH_CPU_ADDRESS_BITS'
   47 |         static constexpr bool   needs_aligned_allocate = alignof(T) > (JPH_CPU_ADDRESS_BITS == 32? 8 : 16);
      |                                                                        ^
# ......
# .....
```

But this is kind of a bummer, more so for rv64 users.

### Steps to reproduce

1. Follow the [instructions for setting up an rv64 cross-compiler](https://docs.godotengine.org/en/latest/contributing/development/compiling/compiling_for_linuxbsd.html#cross-compiling-for-risc-v-devices).
2. Execute the build command there.
3. Wait for failure.

### Minimal reproduction project (MRP)

N/A


Discussion / hints:
CC @mihe @jrouwe 
I don't know if there's any plans upstream to add support for more architectures eventually.

For now the simple solution for us is to disable the `jolt_physics` module on those architectures, I'll do that.
PowerPC support was brought up fairly recently: jrouwe/JoltPhysics#1377

I must admit I didn't realize either of those architectures were supported by Godot.

What kind of devices is RISC-V used for targeting mainly?
It supports WASM without any assembler optimizations, so it should be fairly easy to use the same code path for unsupported archs, something like (not tested on any actual devices):

```diff
diff --git a/thirdparty/jolt_physics/Jolt/Core/Core.h b/thirdparty/jolt_physics/Jolt/Core/Core.h
index a0ad97eeb6..b2c9de20e0 100644
--- a/thirdparty/jolt_physics/Jolt/Core/Core.h
+++ b/thirdparty/jolt_physics/Jolt/Core/Core.h
@@ -202,6 +202,22 @@
 	#if defined(__SSE__) && !defined(JPH_USE_SSE)
 		#define JPH_USE_SSE
 	#endif
+#elif defined(__powerpc__) || defined(_M_PPC)
+	#define JPH_CPU_OTHER
+	#if defined(__powerpc64__) || defined(_M_PPC64) || defined(__ppc64__)
+		#define JPH_CPU_ADDRESS_BITS 64
+	#else
+		#define JPH_CPU_ADDRESS_BITS 32
+	#endif 
+	#define JPH_VECTOR_ALIGNMENT 16
+	#define JPH_DVECTOR_ALIGNMENT 32
+	#define JPH_BREAKPOINT	__builtin_trap()
+#elif defined(__riscv) || defined(__loongarch64)
+	#define JPH_CPU_OTHER
+	#define JPH_CPU_ADDRESS_BITS 64
+	#define JPH_VECTOR_ALIGNMENT 16
+	#define JPH_DVECTOR_ALIGNMENT 32
+	#define JPH_BREAKPOINT	__builtin_trap()
 #else
 	#error Unsupported CPU architecture
 #endif
diff --git a/thirdparty/jolt_physics/Jolt/Core/FPControlWord.h b/thirdparty/jolt_physics/Jolt/Core/FPControlWord.h
index 0c8b3f1ff0..0f8b72343c 100644
--- a/thirdparty/jolt_physics/Jolt/Core/FPControlWord.h
+++ b/thirdparty/jolt_physics/Jolt/Core/FPControlWord.h
@@ -8,7 +8,7 @@
 
 JPH_NAMESPACE_BEGIN
 
-#if defined(JPH_CPU_WASM)
+#if defined(JPH_CPU_WASM) || defined(JPH_CPU_OTHER)
 
 // Not supported
 
diff --git a/thirdparty/jolt_physics/Jolt/Core/FPException.h b/thirdparty/jolt_physics/Jolt/Core/FPException.h
index 3083f05c05..82a6368f79 100644
--- a/thirdparty/jolt_physics/Jolt/Core/FPException.h
+++ b/thirdparty/jolt_physics/Jolt/Core/FPException.h
@@ -10,7 +10,7 @@ JPH_NAMESPACE_BEGIN
 
 #ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED
 
-#if defined(JPH_CPU_WASM)
+#if defined(JPH_CPU_WASM) || defined(JPH_CPU_OTHER)
 
 // Not supported
 class FPExceptionsEnable { };
diff --git a/thirdparty/jolt_physics/Jolt/Core/FPFlushDenormals.h b/thirdparty/jolt_physics/Jolt/Core/FPFlushDenormals.h
index 672a19dbec..5e45c98999 100644
--- a/thirdparty/jolt_physics/Jolt/Core/FPFlushDenormals.h
+++ b/thirdparty/jolt_physics/Jolt/Core/FPFlushDenormals.h
@@ -8,7 +8,7 @@
 
 JPH_NAMESPACE_BEGIN
 
-#if defined(JPH_CPU_WASM)
+#if defined(JPH_CPU_WASM) || defined(JPH_CPU_OTHER)
 
 // Not supported
 class FPFlushDenormals { };
diff --git a/thirdparty/jolt_physics/Jolt/Core/TickCounter.h b/thirdparty/jolt_physics/Jolt/Core/TickCounter.h
index 2b5410e3d9..d961e61077 100644
--- a/thirdparty/jolt_physics/Jolt/Core/TickCounter.h
+++ b/thirdparty/jolt_physics/Jolt/Core/TickCounter.h
@@ -37,7 +37,7 @@ JPH_INLINE uint64 GetProcessorTickCount()
 	return val;
 #elif defined(JPH_CPU_ARM)
 	return 0; // Not supported
-#elif defined(JPH_CPU_WASM)
+#elif defined(JPH_CPU_WASM) || defined(JPH_CPU_OTHER)
 	return 0; // Not supported
 #else
 	#error Undefined
diff --git a/thirdparty/jolt_physics/Jolt/Math/Math.h b/thirdparty/jolt_physics/Jolt/Math/Math.h
index 729d5403e3..24c5cbff7e 100644
--- a/thirdparty/jolt_physics/Jolt/Math/Math.h
+++ b/thirdparty/jolt_physics/Jolt/Math/Math.h
@@ -94,7 +94,7 @@ inline bool IsAligned(T inV, uint64 inAlignment)
 /// Compute number of trailing zero bits (how many low bits are zero)
 inline uint CountTrailingZeros(uint32 inValue)
 {
-#if defined(JPH_CPU_X86) || defined(JPH_CPU_WASM)
+#if defined(JPH_CPU_X86) || defined(JPH_CPU_WASM) || defined(JPH_CPU_OTHER)
 	#if defined(JPH_USE_TZCNT)
 		return _tzcnt_u32(inValue);
 	#elif defined(JPH_COMPILER_MSVC)
@@ -130,7 +130,7 @@ inline uint CountTrailingZeros(uint32 inValue)
 /// Compute the number of leading zero bits (how many high bits are zero)
 inline uint CountLeadingZeros(uint32 inValue)
 {
-#if defined(JPH_CPU_X86) || defined(JPH_CPU_WASM)
+#if defined(JPH_CPU_X86) || defined(JPH_CPU_WASM) || defined(JPH_CPU_OTHER)
 	#if defined(JPH_USE_LZCNT)
 		return _lzcnt_u32(inValue);
 	#elif defined(JPH_COMPILER_MSVC)
```
Based on https://github.com/jrouwe/JoltPhysics/pull/1377, I expect @jrouwe isn't too keen on adding theoretical compilation support for devices which would likely not be powerful enough to decently run Jolt Physics. That's quite fair.

Most push for supporting these architectures comes in my experience mostly from Linux distros, notably Debian, that want to package Godot and still try to support all those architectures for legacy reasons. Like we have ppc32 and ppc64 _theoretical_ support, I've never seen a user that actually used those. So while they probably compile fine on Debian, I'm not sure they can actually run any 3D project.

Usually it's more a theoretical "can it compile?" and not really an effort to actually test the compiled software and make sure it's useful to the theoretical users of those architectures. I've been there myself as a Linux packager so I can understand the rationale, but from an upstream library point of view it's totally to say "nope, not relevant for this lib".

RISC-V and LoongArch are newer systems though, and may (eventually?) be relevant for gaming/simulation purposes and warrant proper support in Jolt. But I think it's fine to disable the module for now on those platforms and wait for demand to actually be there for getting Jolt to run on such devices, with confirmation that it (and Godot itself) would actually run well.
That's fair. In my case, I mostly compile these architecture for completeness sake (having a "complete" Godot installation), I myself probably won't export a game to those architectures, nor I expect them to work (I don't have hardware and software to test it), so pretty much what Akien said.

rv64 seems to be the most viable, because with the instructions above, a user was [able to get the editor to boot up](https://github.com/godotengine/godot/issues/91381#issuecomment-2316663122).
I have a riscv machine that very much can run Godot now. :) It is based on the hifive unmatched (https://www.sifive.com/boards/hifive-unmatched) and mine has a radeon rx580 in it right now.

So, non-theoretical riscv systems do exist that can run Godot now. 
So it looks like supporting RISC-V was not that difficult: https://github.com/jrouwe/JoltPhysics/pull/1400. It even works with cross platform determinism (producing the same simulation results bit for bit when compared with x86/ARM/WASM).

I see that there's also a `qemu-loongarch64` emulator so I'm guessing that it's not too hard to build and validate that one too.

Funny thing is that I looked at godot's supported platforms when discussing https://github.com/jrouwe/JoltPhysics/pull/1377. I didn't see support for PPC, so I dismissed it as not being very important. The platform is a bit more problematic as it is a big-endian architecture. But if the requirements are that it compiles and does not need to run, then I guess that that can be done too.

@jrouwe I think a risc-v port of Jolt would be useful, PPCLE might also be worth doing for the few millionaires that own a Raptor Thalos ][, I don't know if anyone runs ppcbe linux on those.
* RISC-V support has been merged.
* PowerPC support has been merged, only Little Endian mode works atm. Big Endian compiles but fails the unit tests.
LoongArch support has also been added to Jolt. 

Fix patch:
diff --git a/modules/jolt_physics/config.py b/modules/jolt_physics/config.py
index a42f27fbe122..dea89f0618fd 100644
--- a/modules/jolt_physics/config.py
+++ b/modules/jolt_physics/config.py
@@ -1,5 +1,5 @@
 def can_build(env, platform):
-    return not env["disable_3d"]
+    return not env["disable_3d"] and not env["arch"] == "ppc32"
 
 
 def configure(env):
diff --git a/thirdparty/README.md b/thirdparty/README.md
index d0ad28f44b38..4af4368085d4 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -436,7 +436,7 @@ Files generated from upstream source:
 ## jolt_physics
 
 - Upstream: https://github.com/jrouwe/JoltPhysics
-- Version: 5.2.1 (e3d3cdf644389b621914bb6e73d52ee3137591a7, 2024)
+- Version: 5.2.1 (f094082aa2bbfcbebc725dbe8b8f65c7d5152886, 2024)
 - License: MIT
 
 Files extracted from upstream source:
diff --git a/thirdparty/jolt_physics/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h b/thirdparty/jolt_physics/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h
index 15717a2da74b..c0feea7fe20d 100644
--- a/thirdparty/jolt_physics/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h
+++ b/thirdparty/jolt_physics/Jolt/AABBTree/NodeCodec/NodeCodecQuadTreeHalfFloat.h
@@ -254,6 +254,15 @@ class NodeCodecQuadTreeHalfFloat
 					const Node *node = reinterpret_cast<const Node *>(inBufferStart + (node_properties << OFFSET_NON_SIGNIFICANT_BITS));
 
 					// Unpack bounds
+				#ifdef JPH_CPU_BIG_ENDIAN
+					Vec4 bounds_minx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinX[0] + (node->mBoundsMinX[1] << 16), node->mBoundsMinX[2] + (node->mBoundsMinX[3] << 16), 0, 0));
+					Vec4 bounds_miny = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinY[0] + (node->mBoundsMinY[1] << 16), node->mBoundsMinY[2] + (node->mBoundsMinY[3] << 16), 0, 0));
+					Vec4 bounds_minz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMinZ[0] + (node->mBoundsMinZ[1] << 16), node->mBoundsMinZ[2] + (node->mBoundsMinZ[3] << 16), 0, 0));
+
+					Vec4 bounds_maxx = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxX[0] + (node->mBoundsMaxX[1] << 16), node->mBoundsMaxX[2] + (node->mBoundsMaxX[3] << 16), 0, 0));
+					Vec4 bounds_maxy = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxY[0] + (node->mBoundsMaxY[1] << 16), node->mBoundsMaxY[2] + (node->mBoundsMaxY[3] << 16), 0, 0));
+					Vec4 bounds_maxz = HalfFloatConversion::ToFloat(UVec4(node->mBoundsMaxZ[0] + (node->mBoundsMaxZ[1] << 16), node->mBoundsMaxZ[2] + (node->mBoundsMaxZ[3] << 16), 0, 0));
+				#else
 					UVec4 bounds_minxy = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMinX[0]));
 					Vec4 bounds_minx = HalfFloatConversion::ToFloat(bounds_minxy);
 					Vec4 bounds_miny = HalfFloatConversion::ToFloat(bounds_minxy.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
@@ -265,6 +274,7 @@ class NodeCodecQuadTreeHalfFloat
 					UVec4 bounds_maxyz = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&node->mBoundsMaxY[0]));
 					Vec4 bounds_maxy = HalfFloatConversion::ToFloat(bounds_maxyz);
 					Vec4 bounds_maxz = HalfFloatConversion::ToFloat(bounds_maxyz.Swizzle<SWIZZLE_Z, SWIZZLE_W, SWIZZLE_UNUSED, SWIZZLE_UNUSED>());
+				#endif
 
 					// Load properties for 4 children
 					UVec4 properties = UVec4::sLoadInt4(&node->mNodeProperties[0]);
diff --git a/thirdparty/jolt_physics/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h b/thirdparty/jolt_physics/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h
index 2168a5e43a70..b3c33c5155b6 100644
--- a/thirdparty/jolt_physics/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h
+++ b/thirdparty/jolt_physics/Jolt/AABBTree/TriangleCodec/TriangleCodecIndexed8BitPackSOA4Flags.h
@@ -338,7 +338,7 @@ class TriangleCodecIndexed8BitPackSOA4Flags
 	class DecodingContext
 	{
 	private:
-		/// Private helper functions to unpack the 1 vertex of 4 triangles (outX contains the x coordinate of triangle 0 .. 3 etc.)
+		/// Private helper function to unpack the 1 vertex of 4 triangles (outX contains the x coordinate of triangle 0 .. 3 etc.)
 		JPH_INLINE void				Unpack(const VertexData *inVertices, UVec4Arg inIndex, Vec4 &outX, Vec4 &outY, Vec4 &outZ) const
 		{
 			// Get compressed data
@@ -356,6 +356,28 @@ class TriangleCodecIndexed8BitPackSOA4Flags
 			outZ = Vec4::sFusedMultiplyAdd(zc.ToFloat(), mScaleZ, mOffsetZ);
 		}
 
+		/// Private helper function to unpack 4 triangles from a triangle block
+		JPH_INLINE void				Unpack(const TriangleBlock *inBlock, const VertexData *inVertices, Vec4 &outX1, Vec4 &outY1, Vec4 &outZ1, Vec4 &outX2, Vec4 &outY2, Vec4 &outZ2, Vec4 &outX3, Vec4 &outY3, Vec4 &outZ3) const
+		{
+			// Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok)
+			UVec4 indices = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&inBlock->mIndices[0]));
+			UVec4 iv1 = indices.Expand4Byte0();
+			UVec4 iv2 = indices.Expand4Byte4();
+			UVec4 iv3 = indices.Expand4Byte8();
+
+		#ifdef JPH_CPU_BIG_ENDIAN
+			// On big endian systems we need to reverse the bytes
+			iv1 = iv1.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>();
+			iv2 = iv2.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>();
+			iv3 = iv3.Swizzle<SWIZZLE_W, SWIZZLE_Z, SWIZZLE_Y, SWIZZLE_X>();
+		#endif
+
+			// Decompress the triangle data
+			Unpack(inVertices, iv1, outX1, outY1, outZ1);
+			Unpack(inVertices, iv2, outX2, outY2, outZ2);
+			Unpack(inVertices, iv3, outX3, outY3, outZ3);
+		}
+
 	public:
 		JPH_INLINE explicit			DecodingContext(const TriangleHeader *inHeader) :
 			mOffsetX(Vec4::sReplicate(inHeader->mOffset.x)),
@@ -380,17 +402,9 @@ class TriangleCodecIndexed8BitPackSOA4Flags
 
 			do
 			{
-				// Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok)
-				UVec4 indices = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&t->mIndices[0]));
-				UVec4 iv1 = indices.Expand4Byte0();
-				UVec4 iv2 = indices.Expand4Byte4();
-				UVec4 iv3 = indices.Expand4Byte8();
-
-				// Decompress the triangle data
+				// Unpack the vertices for 4 triangles
 				Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z;
-				Unpack(vertices, iv1, v1x, v1y, v1z);
-				Unpack(vertices, iv2, v2x, v2y, v2z);
-				Unpack(vertices, iv3, v3x, v3y, v3z);
+				Unpack(t, vertices, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
 
 				// Transpose it so we get normal vectors
 				Mat44 v1 = Mat44(v1x, v1y, v1z, Vec4::sZero()).Transposed();
@@ -425,17 +439,9 @@ class TriangleCodecIndexed8BitPackSOA4Flags
 			UVec4 start_triangle_idx = UVec4::sZero();
 			do
 			{
-				// Get the indices for the three vertices (reads 4 bytes extra, but these are the flags so that's ok)
-				UVec4 indices = UVec4::sLoadInt4(reinterpret_cast<const uint32 *>(&t->mIndices[0]));
-				UVec4 iv1 = indices.Expand4Byte0();
-				UVec4 iv2 = indices.Expand4Byte4();
-				UVec4 iv3 = indices.Expand4Byte8();
-
-				// Decompress the triangle data
+				// Unpack the vertices for 4 triangles
 				Vec4 v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z;
-				Unpack(vertices, iv1, v1x, v1y, v1z);
-				Unpack(vertices, iv2, v2x, v2y, v2z);
-				Unpack(vertices, iv3, v3x, v3y, v3z);
+				Unpack(t, vertices, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
 
 				// Perform ray vs triangle test
 				Vec4 distance = RayTriangle4(inRayOrigin, inRayDirection, v1x, v1y, v1z, v2x, v2y, v2z, v3x, v3y, v3z);
diff --git a/thirdparty/jolt_physics/Jolt/ConfigurationString.h b/thirdparty/jolt_physics/Jolt/ConfigurationString.h
index 55e5d8c35ff8..1ff1969b2412 100644
--- a/thirdparty/jolt_physics/Jolt/ConfigurationString.h
+++ b/thirdparty/jolt_physics/Jolt/ConfigurationString.h
@@ -14,8 +14,23 @@ inline const char *GetConfigurationString()
 		"x86 "
 #elif defined(JPH_CPU_ARM)
 		"ARM "
-#elif defined(JPH_PLATFORM_WASM)
+#elif defined(JPH_CPU_RISCV)
+		"RISC-V "
+#elif defined(JPH_CPU_PPC)
+		"PowerPC "
+	#ifdef JPH_CPU_BIG_ENDIAN
+		"(Big Endian) "
+	#else
+		"(Little Endian) "
+	#endif
+#elif defined(JPH_CPU_LOONGARCH)
+		"LoongArch "
+#elif defined(JPH_CPU_E2K)
+		"E2K "
+#elif defined(JPH_CPU_WASM)
 		"WASM "
+#else
+	#error Unknown CPU architecture
 #endif
 #if JPH_CPU_ADDRESS_BITS == 64
 		"64-bit "
diff --git a/thirdparty/jolt_physics/Jolt/Core/Array.h b/thirdparty/jolt_physics/Jolt/Core/Array.h
index dd0a9e1ea040..a273f4eb50ff 100644
--- a/thirdparty/jolt_physics/Jolt/Core/Array.h
+++ b/thirdparty/jolt_physics/Jolt/Core/Array.h
@@ -59,7 +59,7 @@ class [[nodiscard]] Array : private Allocator
 			{
 				for (T *destination_end = inDestination + inCount; inDestination < destination_end; ++inDestination, ++inSource)
 				{
-					::new (inDestination) T(std::move(*inSource));
+					new (inDestination) T(std::move(*inSource));
 					inSource->~T();
 				}
 			}
@@ -67,7 +67,7 @@ class [[nodiscard]] Array : private Allocator
 			{
 				for (T *destination = inDestination + inCount - 1, *source = inSource + inCount - 1; destination >= inDestination; --destination, --source)
 				{
-					::new (destination) T(std::move(*source));
+					new (destination) T(std::move(*source));
 					source->~T();
 				}
 			}
@@ -124,7 +124,7 @@ class [[nodiscard]] Array : private Allocator
 
 		if constexpr (!std::is_trivially_constructible<T>())
 			for (T *element = mElements + mSize, *element_end = mElements + inNewSize; element < element_end; ++element)
-				::new (element) T;
+				new (element) T;
 		mSize = inNewSize;
 	}
 
@@ -137,7 +137,7 @@ class [[nodiscard]] Array : private Allocator
 		reserve(inNewSize);
 
 		for (T *element = mElements + mSize, *element_end = mElements + inNewSize; element < element_end; ++element)
-			::new (element) T(inValue);
+			new (element) T(inValue);
 		mSize = inNewSize;
 	}
 
@@ -187,7 +187,7 @@ class [[nodiscard]] Array : private Allocator
 		reserve(size_type(std::distance(inBegin, inEnd)));
 
 		for (Iterator element = inBegin; element != inEnd; ++element)
-			::new (&mElements[mSize++]) T(*element);
+			new (&mElements[mSize++]) T(*element);
 	}
 
 	/// Replace the contents of this array with inList
@@ -197,7 +197,7 @@ class [[nodiscard]] Array : private Allocator
 		reserve(size_type(inList.size()));
 
 		for (const T &v : inList)
-			::new (&mElements[mSize++]) T(v);
+			new (&mElements[mSize++]) T(v);
 	}
 
 	/// Default constructor
@@ -281,7 +281,7 @@ class [[nodiscard]] Array : private Allocator
 		grow();
 
 		T *element = mElements + mSize++;
-		::new (element) T(inValue);
+		new (element) T(inValue);
 	}
 
 	inline void				push_back(T &&inValue)
@@ -289,7 +289,7 @@ class [[nodiscard]] Array : private Allocator
 		grow();
 
 		T *element = mElements + mSize++;
-		::new (element) T(std::move(inValue));
+		new (element) T(std::move(inValue));
 	}
 
 	/// Construct element at the back of the array
@@ -299,7 +299,7 @@ class [[nodiscard]] Array : private Allocator
 		grow();
 
 		T *element = mElements + mSize++;
-		::new (element) T(std::forward<A>(inValue)...);
+		new (element) T(std::forward<A>(inValue)...);
 		return *element;
 	}
 
@@ -365,7 +365,7 @@ class [[nodiscard]] Array : private Allocator
 			move(element_end, element_begin, mSize - first_element);
 
 			for (T *element = element_begin; element < element_end; ++element, ++inBegin)
-				::new (element) T(*inBegin);
+				new (element) T(*inBegin);
 
 			mSize += num_elements;
 		}
@@ -383,7 +383,7 @@ class [[nodiscard]] Array : private Allocator
 		T *element = mElements + first_element;
 		move(element + 1, element, mSize - first_element);
 
-		::new (element) T(inValue);
+		new (element) T(inValue);
 		mSize++;
 	}
 
diff --git a/thirdparty/jolt_physics/Jolt/Core/ByteBuffer.h b/thirdparty/jolt_physics/Jolt/Core/ByteBuffer.h
index 48d19703f3e2..610b15155704 100644
--- a/thirdparty/jolt_physics/Jolt/Core/ByteBuffer.h
+++ b/thirdparty/jolt_physics/Jolt/Core/ByteBuffer.h
@@ -41,7 +41,7 @@ class ByteBuffer : public ByteBufferVector
 
 		// Construct elements
 		for (Type *d = data, *d_end = data + inSize; d < d_end; ++d)
-			::new (d) Type;
+			new (d) Type;
 
 		// Return pointer
 		return data;
diff --git a/thirdparty/jolt_physics/Jolt/Core/Core.h b/thirdparty/jolt_physics/Jolt/Core/Core.h
index a0ad97eeb63c..01b6afbf19f2 100644
--- a/thirdparty/jolt_physics/Jolt/Core/Core.h
+++ b/thirdparty/jolt_physics/Jolt/Core/Core.h
@@ -180,6 +180,18 @@
 		#define JPH_VECTOR_ALIGNMENT 8 // 32-bit ARM does not support aligning on the stack on 16 byte boundaries
 		#define JPH_DVECTOR_ALIGNMENT 8
 	#endif
+#elif defined(__riscv)
+	// RISC-V CPU architecture
+	#define JPH_CPU_RISCV
+	#if __riscv_xlen == 64
+		#define JPH_CPU_ADDRESS_BITS 64
+		#define JPH_VECTOR_ALIGNMENT 16
+		#define JPH_DVECTOR_ALIGNMENT 32
+	#else
+		#define JPH_CPU_ADDRESS_BITS 32
+		#define JPH_VECTOR_ALIGNMENT 16
+		#define JPH_DVECTOR_ALIGNMENT 8
+	#endif
 #elif defined(JPH_PLATFORM_WASM)
 	// WebAssembly CPU architecture
 	#define JPH_CPU_WASM
@@ -191,6 +203,29 @@
 		#define JPH_USE_SSE4_1
 		#define JPH_USE_SSE4_2
 	#endif
+#elif defined(__powerpc__) || defined(__powerpc64__)
+	// PowerPC CPU architecture
+	#define JPH_CPU_PPC
+	#if defined(__powerpc64__)
+		#define JPH_CPU_ADDRESS_BITS 64
+	#else
+		#define JPH_CPU_ADDRESS_BITS 32
+	#endif
+	#ifdef _BIG_ENDIAN
+		#define JPH_CPU_BIG_ENDIAN
+	#endif
+	#define JPH_VECTOR_ALIGNMENT 16
+	#define JPH_DVECTOR_ALIGNMENT 8
+#elif defined(__loongarch__)
+	// LoongArch CPU architecture
+	#define JPH_CPU_LOONGARCH
+	#if defined(__loongarch64)
+		#define JPH_CPU_ADDRESS_BITS 64
+	#else
+		#define JPH_CPU_ADDRESS_BITS 32
+	#endif
+	#define JPH_VECTOR_ALIGNMENT 16
+	#define JPH_DVECTOR_ALIGNMENT 8
 #elif defined(__e2k__)
 	// E2K CPU architecture (MCST Elbrus 2000)
 	#define JPH_CPU_E2K
@@ -358,10 +393,10 @@
 #elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID) || defined(JPH_PLATFORM_MACOS) || defined(JPH_PLATFORM_IOS) || defined(JPH_PLATFORM_FREEBSD)
 	#if defined(JPH_CPU_X86)
 		#define JPH_BREAKPOINT	__asm volatile ("int $0x3")
-	#elif defined(JPH_CPU_ARM)
-		#define JPH_BREAKPOINT	__builtin_trap()
-	#elif defined(JPH_CPU_E2K)
+	#elif defined(JPH_CPU_ARM) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_E2K) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
 		#define JPH_BREAKPOINT	__builtin_trap()
+	#else
+		#error Unknown CPU architecture
 	#endif
 #elif defined(JPH_PLATFORM_WASM)
 	#define JPH_BREAKPOINT		do { } while (false) // Not supported
diff --git a/thirdparty/jolt_physics/Jolt/Core/FPControlWord.h b/thirdparty/jolt_physics/Jolt/Core/FPControlWord.h
index 0c8b3f1ff097..9fceee463698 100644
--- a/thirdparty/jolt_physics/Jolt/Core/FPControlWord.h
+++ b/thirdparty/jolt_physics/Jolt/Core/FPControlWord.h
@@ -126,6 +126,14 @@ class FPControlWord : public NonCopyable
 	uint32		mPrevState;
 };
 
+#elif defined(JPH_CPU_RISCV)
+
+// RISC-V only implements manually checking if exceptions occurred by reading the fcsr register. It doesn't generate exceptions.
+
+#elif defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
+
+// Not implemented right now
+
 #else
 
 #error Unsupported CPU architecture
diff --git a/thirdparty/jolt_physics/Jolt/Core/FPException.h b/thirdparty/jolt_physics/Jolt/Core/FPException.h
index 3083f05c0508..ef757802424e 100644
--- a/thirdparty/jolt_physics/Jolt/Core/FPException.h
+++ b/thirdparty/jolt_physics/Jolt/Core/FPException.h
@@ -56,6 +56,14 @@ class FPExceptionDisableInvalid : public FPControlWord<0, FP_IOE> { };
 /// Disable division by zero floating point exceptions
 class FPExceptionDisableDivByZero : public FPControlWord<0, FP_DZE> { };
 
+#elif defined(JPH_CPU_RISCV)
+
+#error "RISC-V only implements manually checking if exceptions occurred by reading the fcsr register. It doesn't generate exceptions. JPH_FLOATING_POINT_EXCEPTIONS_ENABLED must be disabled."
+
+#elif defined(JPH_CPU_PPC)
+
+#error PowerPC floating point exception handling to be implemented. JPH_FLOATING_POINT_EXCEPTIONS_ENABLED must be disabled.
+
 #else
 
 #error Unsupported CPU architecture
diff --git a/thirdparty/jolt_physics/Jolt/Core/FPFlushDenormals.h b/thirdparty/jolt_physics/Jolt/Core/FPFlushDenormals.h
index 672a19dbecf5..74a2c10b3541 100644
--- a/thirdparty/jolt_physics/Jolt/Core/FPFlushDenormals.h
+++ b/thirdparty/jolt_physics/Jolt/Core/FPFlushDenormals.h
@@ -8,7 +8,7 @@
 
 JPH_NAMESPACE_BEGIN
 
-#if defined(JPH_CPU_WASM)
+#if defined(JPH_CPU_WASM) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
 
 // Not supported
 class FPFlushDenormals { };
@@ -21,6 +21,8 @@ class FPFlushDenormals : public FPControlWord<_MM_FLUSH_ZERO_ON, _MM_FLUSH_ZERO_
 
 #elif defined(JPH_CPU_ARM) && defined(JPH_COMPILER_MSVC)
 
+/// Helper class that needs to be put on the stack to enable flushing denormals to zero
+/// This can make floating point operations much faster when working with very small numbers
 class FPFlushDenormals : public FPControlWord<_DN_FLUSH, _MCW_DN> { };
 
 #elif defined(JPH_CPU_ARM)
diff --git a/thirdparty/jolt_physics/Jolt/Core/FixedSizeFreeList.inl b/thirdparty/jolt_physics/Jolt/Core/FixedSizeFreeList.inl
index 29d4734c009f..3fe40b8925cc 100644
--- a/thirdparty/jolt_physics/Jolt/Core/FixedSizeFreeList.inl
+++ b/thirdparty/jolt_physics/Jolt/Core/FixedSizeFreeList.inl
@@ -79,7 +79,7 @@ uint32 FixedSizeFreeList<Object>::ConstructObject(Parameters &&... inParameters)
 			// Allocation successful
 			JPH_IF_ENABLE_ASSERTS(mNumFreeObjects.fetch_sub(1, memory_order_relaxed);)
 			ObjectStorage &storage = GetStorage(first_free);
-			::new (&storage.mObject) Object(std::forward<Parameters>(inParameters)...);
+			new (&storage.mObject) Object(std::forward<Parameters>(inParameters)...);
 			storage.mNextFreeObject.store(first_free, memory_order_release);
 			return first_free;
 		}
@@ -97,7 +97,7 @@ uint32 FixedSizeFreeList<Object>::ConstructObject(Parameters &&... inParameters)
 				// Allocation successful
 				JPH_IF_ENABLE_ASSERTS(mNumFreeObjects.fetch_sub(1, memory_order_relaxed);)
 				ObjectStorage &storage = GetStorage(first_free);
-				::new (&storage.mObject) Object(std::forward<Parameters>(inParameters)...);
+				new (&storage.mObject) Object(std::forward<Parameters>(inParameters)...);
 				storage.mNextFreeObject.store(first_free, memory_order_release);
 				return first_free;
 			}
diff --git a/thirdparty/jolt_physics/Jolt/Core/HashCombine.h b/thirdparty/jolt_physics/Jolt/Core/HashCombine.h
index e04b54b7443f..ab620841dc81 100644
--- a/thirdparty/jolt_physics/Jolt/Core/HashCombine.h
+++ b/thirdparty/jolt_physics/Jolt/Core/HashCombine.h
@@ -17,8 +17,8 @@ inline uint64 HashBytes(const void *inData, uint inSize, uint64 inSeed = 0xcbf29
 	uint64 hash = inSeed;
 	for (const uint8 *data = reinterpret_cast<const uint8 *>(inData); data < reinterpret_cast<const uint8 *>(inData) + inSize; ++data)
 	{
-		hash = hash ^ uint64(*data);
-		hash = hash * 0x100000001b3UL;
+		hash ^= uint64(*data);
+		hash *= 0x100000001b3UL;
 	}
 	return hash;
 }
@@ -31,7 +31,7 @@ constexpr uint64 HashString(const char *inString, uint64 inSeed = 0xcbf29ce48422
 	for (const char *c = inString; *c != 0; ++c)
 	{
 		hash ^= uint64(*c);
-		hash = hash * 0x100000001b3UL;
+		hash *= 0x100000001b3UL;
 	}
 	return hash;
 }
@@ -142,12 +142,33 @@ JPH_DEFINE_TRIVIAL_HASH(int)
 JPH_DEFINE_TRIVIAL_HASH(uint32)
 JPH_DEFINE_TRIVIAL_HASH(uint64)
 
-/// @brief Helper function that hashes a single value into ioSeed
-/// Taken from: https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x
+/// Helper function that hashes a single value into ioSeed
+/// Based on https://github.com/jonmaiga/mx3 by Jon Maiga
 template <typename T>
 inline void HashCombine(uint64 &ioSeed, const T &inValue)
 {
-	ioSeed ^= Hash<T> { } (inValue) + 0x9e3779b9 + (ioSeed << 6) + (ioSeed >> 2);
+	constexpr uint64 c = 0xbea225f9eb34556dUL;
+
+	uint64 h = ioSeed;
+	uint64 x = Hash<T> { } (inValue);
+
+	// See: https://github.com/jonmaiga/mx3/blob/master/mx3.h
+	// mix_stream(h, x)
+	x *= c;
+	x ^= x >> 39;
+	h += x * c;
+	h *= c;
+
+	// mix(h)
+	h ^= h >> 32;
+	h *= c;
+	h ^= h >> 29;
+	h *= c;
+	h ^= h >> 32;
+	h *= c;
+	h ^= h >> 29;
+
+	ioSeed = h;
 }
 
 /// Hash combiner to use a custom struct in an unordered map or set
@@ -174,11 +195,6 @@ inline uint64 HashCombineArgs(const FirstValue &inFirstValue, Values... inValues
 	return seed;
 }
 
-JPH_NAMESPACE_END
-
-JPH_SUPPRESS_WARNING_PUSH
-JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
-
 #define JPH_MAKE_HASH_STRUCT(type, name, ...)				\
 	struct [[nodiscard]] name								\
 	{														\
@@ -188,25 +204,31 @@ JPH_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic")
 		}													\
 	};
 
-#define JPH_MAKE_HASHABLE(type, ...)						\
+#define JPH_MAKE_STD_HASH(type)								\
 	JPH_SUPPRESS_WARNING_PUSH								\
 	JPH_SUPPRESS_WARNINGS									\
-	namespace JPH											\
-	{														\
-		template<>											\
-		JPH_MAKE_HASH_STRUCT(type, Hash<type>, __VA_ARGS__) \
-	}														\
 	namespace std											\
 	{														\
 		template<>											\
 		struct [[nodiscard]] hash<type>						\
 		{													\
-			std::size_t operator()(const type &t) const		\
+			size_t operator()(const type &t) const			\
 			{												\
-				return std::size_t(::JPH::Hash<type>{ }(t));\
+				return size_t(::JPH::Hash<type>{ }(t));		\
 			}												\
 		};													\
 	}														\
 	JPH_SUPPRESS_WARNING_POP
 
-JPH_SUPPRESS_WARNING_POP
+#define JPH_MAKE_HASHABLE(type, ...)						\
+	JPH_SUPPRESS_WARNING_PUSH								\
+	JPH_SUPPRESS_WARNINGS									\
+	namespace JPH											\
+	{														\
+		template<>											\
+		JPH_MAKE_HASH_STRUCT(type, Hash<type>, __VA_ARGS__) \
+	}														\
+	JPH_SUPPRESS_WARNING_POP								\
+	JPH_MAKE_STD_HASH(type)
+
+JPH_NAMESPACE_END
diff --git a/thirdparty/jolt_physics/Jolt/Core/HashTable.h b/thirdparty/jolt_physics/Jolt/Core/HashTable.h
index baca08255baa..9b69f9d08401 100644
--- a/thirdparty/jolt_physics/Jolt/Core/HashTable.h
+++ b/thirdparty/jolt_physics/Jolt/Core/HashTable.h
@@ -175,7 +175,7 @@ class HashTable
 		uint index = 0;
 		for (const uint8 *control = mControl, *control_end = mControl + mMaxSize; control != control_end; ++control, ++index)
 			if (*control & cBucketUsed)
-				::new (mData + index) KeyValue(inRHS.mData[index]);
+				new (mData + index) KeyValue(inRHS.mData[index]);
 		mSize = inRHS.mSize;
 	}
 
@@ -216,7 +216,7 @@ class HashTable
 					KeyValue *element = old_data + i;
 					JPH_IF_ENABLE_ASSERTS(bool inserted =) InsertKey</* InsertAfterGrow= */ true>(HashTableDetail::sGetKey(*element), index);
 					JPH_ASSERT(inserted);
-					::new (mData + index) KeyValue(std::move(*element));
+					new (mData + index) KeyValue(std::move(*element));
 					element->~KeyValue();
 				}
 
@@ -601,7 +601,7 @@ class HashTable
 		size_type index;
 		bool inserted = InsertKey(HashTableDetail::sGetKey(inValue), index);
 		if (inserted)
-			::new (mData + index) KeyValue(inValue);
+			new (mData + index) KeyValue(inValue);
 		return std::make_pair(iterator(this, index), inserted);
 	}
 
@@ -800,7 +800,7 @@ class HashTable
 						// There's an empty bucket, move us there
 						SetControlValue(dst, src_control);
 						SetControlValue(src, cBucketEmpty);
-						::new (mData + dst) KeyValue(std::move(mData[src]));
+						new (mData + dst) KeyValue(std::move(mData[src]));
 						mData[src].~KeyValue();
 						break;
 					}
diff --git a/thirdparty/jolt_physics/Jolt/Core/Memory.h b/thirdparty/jolt_physics/Jolt/Core/Memory.h
index d5b7c6a308be..b5f318db1cfb 100644
--- a/thirdparty/jolt_physics/Jolt/Core/Memory.h
+++ b/thirdparty/jolt_physics/Jolt/Core/Memory.h
@@ -36,7 +36,11 @@ JPH_EXPORT void RegisterDefaultAllocator();
 	JPH_INLINE void *operator new (size_t inCount, std::align_val_t inAlignment)				{ return JPH::AlignedAllocate(inCount, static_cast<size_t>(inAlignment)); } \
 	JPH_INLINE void operator delete (void *inPointer, [[maybe_unused]] std::align_val_t inAlignment) noexcept	{ JPH::AlignedFree(inPointer); } \
 	JPH_INLINE void *operator new[] (size_t inCount, std::align_val_t inAlignment)				{ return JPH::AlignedAllocate(inCount, static_cast<size_t>(inAlignment)); } \
-	JPH_INLINE void operator delete[] (void *inPointer, [[maybe_unused]] std::align_val_t inAlignment) noexcept	{ JPH::AlignedFree(inPointer); }
+	JPH_INLINE void operator delete[] (void *inPointer, [[maybe_unused]] std::align_val_t inAlignment) noexcept	{ JPH::AlignedFree(inPointer); } \
+	JPH_INLINE void *operator new ([[maybe_unused]] size_t inCount, void *inPointer) noexcept	{ return inPointer; } \
+	JPH_INLINE void operator delete ([[maybe_unused]] void *inPointer, [[maybe_unused]] void *inPlace) noexcept { /* Do nothing */ } \
+	JPH_INLINE void *operator new[] ([[maybe_unused]] size_t inCount, void *inPointer) noexcept	{ return inPointer; } \
+	JPH_INLINE void operator delete[] ([[maybe_unused]] void *inPointer, [[maybe_unused]] void *inPlace) noexcept { /* Do nothing */ }
 
 #else
 
diff --git a/thirdparty/jolt_physics/Jolt/Core/Result.h b/thirdparty/jolt_physics/Jolt/Core/Result.h
index ad2eab164bdf..282b35adbb08 100644
--- a/thirdparty/jolt_physics/Jolt/Core/Result.h
+++ b/thirdparty/jolt_physics/Jolt/Core/Result.h
@@ -21,11 +21,11 @@ class Result
 		switch (inRHS.mState)
 		{
 		case EState::Valid:
-			::new (&mResult) Type (inRHS.mResult);
+			new (&mResult) Type (inRHS.mResult);
 			break;
 
 		case EState::Error:
-			::new (&mError) String(inRHS.mError);
+			new (&mError) String(inRHS.mError);
 			break;
 
 		case EState::Invalid:
@@ -40,11 +40,11 @@ class Result
 		switch (inRHS.mState)
 		{
 		case EState::Valid:
-			::new (&mResult) Type (std::move(inRHS.mResult));
+			new (&mResult) Type (std::move(inRHS.mResult));
 			break;
 
 		case EState::Error:
-			::new (&mError) String(std::move(inRHS.mError));
+			new (&mError) String(std::move(inRHS.mError));
 			break;
 
 		case EState::Invalid:
@@ -67,11 +67,11 @@ class Result
 		switch (inRHS.mState)
 		{
 		case EState::Valid:
-			::new (&mResult) Type (inRHS.mResult);
+			new (&mResult) Type (inRHS.mResult);
 			break;
 
 		case EState::Error:
-			::new (&mError) String(inRHS.mError);
+			new (&mError) String(inRHS.mError);
 			break;
 
 		case EState::Invalid:
@@ -91,11 +91,11 @@ class Result
 		switch (inRHS.mState)
 		{
 		case EState::Valid:
-			::new (&mResult) Type (std::move(inRHS.mResult));
+			new (&mResult) Type (std::move(inRHS.mResult));
 			break;
 
 		case EState::Error:
-			::new (&mError) String(std::move(inRHS.mError));
+			new (&mError) String(std::move(inRHS.mError));
 			break;
 
 		case EState::Invalid:
@@ -137,10 +137,10 @@ class Result
 	const Type &		Get() const									{ JPH_ASSERT(IsValid()); return mResult; }
 
 	/// Set the result value
-	void				Set(const Type &inResult)					{ Clear(); ::new (&mResult) Type(inResult); mState = EState::Valid; }
+	void				Set(const Type &inResult)					{ Clear(); new (&mResult) Type(inResult); mState = EState::Valid; }
 
 	/// Set the result value (move value)
-	void				Set(Type &&inResult)						{ Clear(); ::new (&mResult) Type(std::move(inResult)); mState = EState::Valid; }
+	void				Set(Type &&inResult)						{ Clear(); new (&mResult) Type(std::move(inResult)); mState = EState::Valid; }
 
 	/// Check if we had an error
 	bool				HasError() const							{ return mState == EState::Error; }
@@ -149,9 +149,9 @@ class Result
 	const String &		GetError() const							{ JPH_ASSERT(HasError()); return mError; }
 
 	/// Set an error value
-	void				SetError(const char *inError)				{ Clear(); ::new (&mError) String(inError); mState = EState::Error; }
-	void				SetError(const string_view &inError)		{ Clear(); ::new (&mError) String(inError); mState = EState::Error; }
-	void				SetError(String &&inError)					{ Clear(); ::new (&mError) String(std::move(inError)); mState = EState::Error; }
+	void				SetError(const char *inError)				{ Clear(); new (&mError) String(inError); mState = EState::Error; }
+	void				SetError(const string_view &inError)		{ Clear(); new (&mError) String(inError); mState = EState::Error; }
+	void				SetError(String &&inError)					{ Clear(); new (&mError) String(std::move(inError)); mState = EState::Error; }
 
 private:
 	union
diff --git a/thirdparty/jolt_physics/Jolt/Core/StaticArray.h b/thirdparty/jolt_physics/Jolt/Core/StaticArray.h
index fddaaa37a2c2..421bf680f59d 100644
--- a/thirdparty/jolt_physics/Jolt/Core/StaticArray.h
+++ b/thirdparty/jolt_physics/Jolt/Core/StaticArray.h
@@ -27,7 +27,7 @@ class [[nodiscard]] StaticArray
 	{
 		JPH_ASSERT(inList.size() <= N);
 		for (const T &v : inList)
-			::new (reinterpret_cast<T *>(&mElements[mSize++])) T(v);
+			new (reinterpret_cast<T *>(&mElements[mSize++])) T(v);
 	}
 
 	/// Copy constructor
@@ -35,7 +35,7 @@ class [[nodiscard]] StaticArray
 	{
 		while (mSize < inRHS.mSize)
 		{
-			::new (&mElements[mSize]) T(inRHS[mSize]);
+			new (&mElements[mSize]) T(inRHS[mSize]);
 			++mSize;
 		}
 	}
@@ -61,7 +61,7 @@ class [[nodiscard]] StaticArray
 	void				push_back(const T &inElement)
 	{
 		JPH_ASSERT(mSize < N);
-		::new (&mElements[mSize++]) T(inElement);
+		new (&mElements[mSize++]) T(inElement);
 	}
 
 	/// Construct element at the back of the array
@@ -69,7 +69,7 @@ class [[nodiscard]] StaticArray
 	void				emplace_back(A &&... inElement)
 	{
 		JPH_ASSERT(mSize < N);
-		::new (&mElements[mSize++]) T(std::forward<A>(inElement)...);
+		new (&mElements[mSize++]) T(std::forward<A>(inElement)...);
 	}
 
 	/// Remove element from the back of the array
@@ -103,7 +103,7 @@ class [[nodiscard]] StaticArray
 		JPH_ASSERT(inNewSize <= N);
 		if constexpr (!std::is_trivially_constructible<T>())
 			for (T *element = reinterpret_cast<T *>(mElements) + mSize, *element_end = reinterpret_cast<T *>(mElements) + inNewSize; element < element_end; ++element)
-				::new (element) T;
+				new (element) T;
 		if constexpr (!std::is_trivially_destructible<T>())
 			for (T *element = reinterpret_cast<T *>(mElements) + inNewSize, *element_end = reinterpret_cast<T *>(mElements) + mSize; element < element_end; ++element)
 				element->~T();
@@ -232,7 +232,7 @@ class [[nodiscard]] StaticArray
 
 			while (mSize < rhs_size)
 			{
-				::new (&mElements[mSize]) T(inRHS[mSize]);
+				new (&mElements[mSize]) T(inRHS[mSize]);
 				++mSize;
 			}
 		}
@@ -253,7 +253,7 @@ class [[nodiscard]] StaticArray
 
 			while (mSize < rhs_size)
 			{
-				::new (&mElements[mSize]) T(inRHS[mSize]);
+				new (&mElements[mSize]) T(inRHS[mSize]);
 				++mSize;
 			}
 		}
diff --git a/thirdparty/jolt_physics/Jolt/Core/TickCounter.h b/thirdparty/jolt_physics/Jolt/Core/TickCounter.h
index 2b5410e3d9f9..7701bd2dcd8a 100644
--- a/thirdparty/jolt_physics/Jolt/Core/TickCounter.h
+++ b/thirdparty/jolt_physics/Jolt/Core/TickCounter.h
@@ -35,9 +35,7 @@ JPH_INLINE uint64 GetProcessorTickCount()
 	uint64 val;
 	asm volatile("mrs %0, cntvct_el0" : "=r" (val));
 	return val;
-#elif defined(JPH_CPU_ARM)
-	return 0; // Not supported
-#elif defined(JPH_CPU_WASM)
+#elif defined(JPH_CPU_ARM) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_WASM) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
 	return 0; // Not supported
 #else
 	#error Undefined
diff --git a/thirdparty/jolt_physics/Jolt/Core/UnorderedMap.h b/thirdparty/jolt_physics/Jolt/Core/UnorderedMap.h
index f876e2849e20..236eaaa69f28 100644
--- a/thirdparty/jolt_physics/Jolt/Core/UnorderedMap.h
+++ b/thirdparty/jolt_physics/Jolt/Core/UnorderedMap.h
@@ -42,7 +42,7 @@ class UnorderedMap : public HashTable<Key, std::pair<Key, Value>, UnorderedMapDe
 		bool inserted = this->InsertKey(inKey, index);
 		value_type &key_value = this->GetElement(index);
 		if (inserted)
-			::new (&key_value) value_type(inKey, Value());
+			new (&key_value) value_type(inKey, Value());
 		return key_value.second;
 	}
 
@@ -52,7 +52,7 @@ class UnorderedMap : public HashTable<Key, std::pair<Key, Value>, UnorderedMapDe
 		size_type index;
 		bool inserted = this->InsertKey(inKey, index);
 		if (inserted)
-			::new (&this->GetElement(index)) value_type(std::piecewise_construct, std::forward_as_tuple(inKey), std::forward_as_tuple(std::forward<Args>(inArgs)...));
+			new (&this->GetElement(index)) value_type(std::piecewise_construct, std::forward_as_tuple(inKey), std::forward_as_tuple(std::forward<Args>(inArgs)...));
 		return std::make_pair(iterator(this, index), inserted);
 	}
 
@@ -62,7 +62,7 @@ class UnorderedMap : public HashTable<Key, std::pair<Key, Value>, UnorderedMapDe
 		size_type index;
 		bool inserted = this->InsertKey(inKey, index);
 		if (inserted)
-			::new (&this->GetElement(index)) value_type(std::piecewise_construct, std::forward_as_tuple(std::move(inKey)), std::forward_as_tuple(std::forward<Args>(inArgs)...));
+			new (&this->GetElement(index)) value_type(std::piecewise_construct, std::forward_as_tuple(std::move(inKey)), std::forward_as_tuple(std::forward<Args>(inArgs)...));
 		return std::make_pair(iterator(this, index), inserted);
 	}
 
diff --git a/thirdparty/jolt_physics/Jolt/Geometry/Ellipse.h b/thirdparty/jolt_physics/Jolt/Geometry/Ellipse.h
index bfa508faae86..466d508bb87d 100644
--- a/thirdparty/jolt_physics/Jolt/Geometry/Ellipse.h
+++ b/thirdparty/jolt_physics/Jolt/Geometry/Ellipse.h
@@ -38,7 +38,7 @@ class Ellipse
 		// <=> (x', y') = (a^2 x / (t + a^2), b^2 y / (t + b^2))
 		// Requiring point to be on ellipse (substituting into [1]): g(t) = (a x / (t + a^2))^2 + (b y / (t + b^2))^2 - 1 = 0
 
-		// Newton raphson iteration, starting at t = 0
+		// Newton Raphson iteration, starting at t = 0
 		float t = 0.0f;
 		for (;;)
 		{
diff --git a/thirdparty/jolt_physics/Jolt/Geometry/IndexedTriangle.h b/thirdparty/jolt_physics/Jolt/Geometry/IndexedTriangle.h
index 7ebffc29c514..f118dae7495e 100644
--- a/thirdparty/jolt_physics/Jolt/Geometry/IndexedTriangle.h
+++ b/thirdparty/jolt_physics/Jolt/Geometry/IndexedTriangle.h
@@ -65,6 +65,13 @@ class IndexedTriangleNoMaterial
 		return (Vec3(inVertices[mIdx[0]]) + Vec3(inVertices[mIdx[1]]) + Vec3(inVertices[mIdx[2]])) / 3.0f;
 	}
 
+	/// Get the hash value of this structure
+	uint64			GetHash() const
+	{
+		static_assert(sizeof(IndexedTriangleNoMaterial) == 3 * sizeof(uint32), "Class should have no padding");
+		return HashBytes(this, sizeof(IndexedTriangleNoMaterial));
+	}
+
 	uint32			mIdx[3];
 };
 
@@ -102,6 +109,13 @@ class IndexedTriangle : public IndexedTriangleNoMaterial
 		}
 	}
 
+	/// Get the hash value of this structure
+	uint64			GetHash() const
+	{
+		static_assert(sizeof(IndexedTriangle) == 5 * sizeof(uint32), "Class should have no padding");
+		return HashBytes(this, sizeof(IndexedTriangle));
+	}
+
 	uint32			mMaterialIndex = 0;
 	uint32			mUserData = 0;				///< User data that can be used for anything by the application, e.g. for tracking the original index of the triangle
 };
@@ -111,6 +125,6 @@ using IndexedTriangleList = Array<IndexedTriangle>;
 
 JPH_NAMESPACE_END
 
-// Create a std::hash/JPH::Hash for IndexedTriangleNoMaterial and IndexedTriangle
-JPH_MAKE_HASHABLE(JPH::IndexedTriangleNoMaterial, t.mIdx[0], t.mIdx[1], t.mIdx[2])
-JPH_MAKE_HASHABLE(JPH::IndexedTriangle, t.mIdx[0], t.mIdx[1], t.mIdx[2], t.mMaterialIndex, t.mUserData)
+// Create a std::hash for IndexedTriangleNoMaterial and IndexedTriangle
+JPH_MAKE_STD_HASH(JPH::IndexedTriangleNoMaterial)
+JPH_MAKE_STD_HASH(JPH::IndexedTriangle)
diff --git a/thirdparty/jolt_physics/Jolt/Geometry/RayAABox.h b/thirdparty/jolt_physics/Jolt/Geometry/RayAABox.h
index 4506fadbb75b..31970e7c1f67 100644
--- a/thirdparty/jolt_physics/Jolt/Geometry/RayAABox.h
+++ b/thirdparty/jolt_physics/Jolt/Geometry/RayAABox.h
@@ -36,7 +36,7 @@ JPH_INLINE float RayAABox(Vec3Arg inOrigin, const RayInvDirection &inInvDirectio
 	Vec3 flt_min = Vec3::sReplicate(-FLT_MAX);
 	Vec3 flt_max = Vec3::sReplicate(FLT_MAX);
 
-	// Test against all three axii simultaneously.
+	// Test against all three axes simultaneously.
 	Vec3 t1 = (inBoundsMin - inOrigin) * inInvDirection.mInvDirection;
 	Vec3 t2 = (inBoundsMax - inOrigin) * inInvDirection.mInvDirection;
 
@@ -90,7 +90,7 @@ JPH_INLINE Vec4 RayAABox4(Vec3Arg inOrigin, const RayInvDirection &inInvDirectio
 	Vec4 invdiry = inInvDirection.mInvDirection.SplatY();
 	Vec4 invdirz = inInvDirection.mInvDirection.SplatZ();
 
-	// Test against all three axii simultaneously.
+	// Test against all three axes simultaneously.
 	Vec4 t1x = (inBoundsMinX - originx) * invdirx;
 	Vec4 t1y = (inBoundsMinY - originy) * invdiry;
 	Vec4 t1z = (inBoundsMinZ - originz) * invdirz;
@@ -139,7 +139,7 @@ JPH_INLINE void RayAABox(Vec3Arg inOrigin, const RayInvDirection &inInvDirection
 	Vec3 flt_min = Vec3::sReplicate(-FLT_MAX);
 	Vec3 flt_max = Vec3::sReplicate(FLT_MAX);
 
-	// Test against all three axii simultaneously.
+	// Test against all three axes simultaneously.
 	Vec3 t1 = (inBoundsMin - inOrigin) * inInvDirection.mInvDirection;
 	Vec3 t2 = (inBoundsMax - inOrigin) * inInvDirection.mInvDirection;
 
@@ -178,7 +178,7 @@ JPH_INLINE bool RayAABoxHits(Vec3Arg inOrigin, const RayInvDirection &inInvDirec
 	Vec3 flt_min = Vec3::sReplicate(-FLT_MAX);
 	Vec3 flt_max = Vec3::sReplicate(FLT_MAX);
 
-	// Test against all three axii simultaneously.
+	// Test against all three axes simultaneously.
 	Vec3 t1 = (inBoundsMin - inOrigin) * inInvDirection.mInvDirection;
 	Vec3 t2 = (inBoundsMax - inOrigin) * inInvDirection.mInvDirection;
 
diff --git a/thirdparty/jolt_physics/Jolt/Math/EigenValueSymmetric.h b/thirdparty/jolt_physics/Jolt/Math/EigenValueSymmetric.h
index 920ddb9c9e72..dab0e69029fa 100644
--- a/thirdparty/jolt_physics/Jolt/Math/EigenValueSymmetric.h
+++ b/thirdparty/jolt_physics/Jolt/Math/EigenValueSymmetric.h
@@ -11,7 +11,7 @@ JPH_NAMESPACE_BEGIN
 /// Function to determine the eigen vectors and values of a N x N real symmetric matrix
 /// by Jacobi transformations. This method is most suitable for N < 10.
 ///
-/// Taken and adapted from Numerical Recipies paragraph 11.1
+/// Taken and adapted from Numerical Recipes paragraph 11.1
 ///
 /// An eigen vector is a vector v for which \f$A \: v = \lambda \: v\f$
 ///
@@ -95,7 +95,7 @@ bool EigenValueSymmetric(const Matrix &inMatrix, Matrix &outEigVec, Vector &outE
 
 		// On the first three sweeps use a fraction of the sum of the off diagonal elements as threshold
 		// Note that we pick a minimum threshold of FLT_MIN because dividing by a denormalized number is likely to result in infinity.
-		float tresh = sweep < 4? 0.2f * avg_sm : FLT_MIN; // Original code: 0.0f instead of FLT_MIN
+		float thresh = sweep < 4? 0.2f * avg_sm : FLT_MIN; // Original code: 0.0f instead of FLT_MIN
 
 		for (uint ip = 0; ip < n - 1; ++ip)
 			for (uint iq = ip + 1; iq < n; ++iq)
@@ -114,7 +114,7 @@ bool EigenValueSymmetric(const Matrix &inMatrix, Matrix &outEigVec, Vector &outE
 				{
 					a_pq = 0.0f;
 				}
-				else if (abs_a_pq > tresh)
+				else if (abs_a_pq > thresh)
 				{
 					float h = eigval_q - eigval_p;
 					float abs_h = abs(h);
diff --git a/thirdparty/jolt_physics/Jolt/Math/GaussianElimination.h b/thirdparty/jolt_physics/Jolt/Math/GaussianElimination.h
index f986cf3b3b07..5b4ce098ec77 100644
--- a/thirdparty/jolt_physics/Jolt/Math/GaussianElimination.h
+++ b/thirdparty/jolt_physics/Jolt/Math/GaussianElimination.h
@@ -14,7 +14,7 @@ JPH_NAMESPACE_BEGIN
 /// Set A to the matrix to invert, set B to identity and let GaussianElimination solve
 /// the equation, on return B will be the inverse of A. And A is destroyed.
 ///
-/// Taken and adapted from Numerical Recipies in C paragraph 2.1
+/// Taken and adapted from Numerical Recipes in C paragraph 2.1
 template <class MatrixA, class MatrixB>
 bool GaussianElimination(MatrixA &ioA, MatrixB &ioB, float inTolerance = 1.0e-16f)
 {
diff --git a/thirdparty/jolt_physics/Jolt/Math/Math.h b/thirdparty/jolt_physics/Jolt/Math/Math.h
index 729d5403e393..221176eead7b 100644
--- a/thirdparty/jolt_physics/Jolt/Math/Math.h
+++ b/thirdparty/jolt_physics/Jolt/Math/Math.h
@@ -72,7 +72,7 @@ JPH_INLINE constexpr T Sign(T inV)
 template <typename T>
 constexpr bool IsPowerOf2(T inV)
 {
-	return (inV & (inV - 1)) == 0;
+	return inV > 0 && (inV & (inV - 1)) == 0;
 }
 
 /// Align inV up to the next inAlignment bytes
@@ -120,8 +120,8 @@ inline uint CountTrailingZeros(uint32 inValue)
 			return 32;
 		return __builtin_ctz(inValue);
 	#endif
-#elif defined(JPH_CPU_E2K)
-		return inValue ? __builtin_ctz(inValue) : 32;
+#elif defined(JPH_CPU_E2K) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
+	return inValue ? __builtin_ctz(inValue) : 32;
 #else
 	#error Undefined
 #endif
@@ -150,8 +150,8 @@ inline uint CountLeadingZeros(uint32 inValue)
 	#else
 		return __builtin_clz(inValue);
 	#endif
-#elif defined(JPH_CPU_E2K)
-		return inValue ? __builtin_clz(inValue) : 32;
+#elif defined(JPH_CPU_E2K) || defined(JPH_CPU_RISCV) || defined(JPH_CPU_PPC) || defined(JPH_CPU_LOONGARCH)
+	return inValue ? __builtin_clz(inValue) : 32;
 #else
 	#error Undefined
 #endif
diff --git a/thirdparty/jolt_physics/Jolt/Math/Vec3.inl b/thirdparty/jolt_physics/Jolt/Math/Vec3.inl
index 5a47f40ce6ba..dba99360d30b 100644
--- a/thirdparty/jolt_physics/Jolt/Math/Vec3.inl
+++ b/thirdparty/jolt_physics/Jolt/Math/Vec3.inl
@@ -64,9 +64,7 @@ Vec3::Vec3(const Float3 &inV)
 	mF32[0] = inV[0];
 	mF32[1] = inV[1];
 	mF32[2] = inV[2];
-	#ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED
-		mF32[3] = inV[2];
-	#endif
+	mF32[3] = inV[2]; // Not strictly needed when JPH_FLOATING_POINT_EXCEPTIONS_ENABLED is off but prevents warnings about uninitialized variables
 #endif
 }
 
@@ -82,9 +80,7 @@ Vec3::Vec3(float inX, float inY, float inZ)
 	mF32[0] = inX;
 	mF32[1] = inY;
 	mF32[2] = inZ;
-	#ifdef JPH_FLOATING_POINT_EXCEPTIONS_ENABLED
-		mF32[3] = inZ;
-	#endif
+	mF32[3] = inZ; // Not strictly needed when JPH_FLOATING_POINT_EXCEPTIONS_ENABLED is off but prevents warnings about uninitialized variables
 #endif
 }
 
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Body/MotionQuality.h b/thirdparty/jolt_physics/Jolt/Physics/Body/MotionQuality.h
index b1ba343c06ac..6d944bda91cb 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Body/MotionQuality.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/Body/MotionQuality.h
@@ -9,7 +9,7 @@ JPH_NAMESPACE_BEGIN
 /// Motion quality, or how well it detects collisions when it has a high velocity
 enum class EMotionQuality : uint8
 {
-	/// Update the body in discrete steps. Body will tunnel throuh thin objects if its velocity is high enough.
+	/// Update the body in discrete steps. Body will tunnel through thin objects if its velocity is high enough.
 	/// This is the cheapest way of simulating a body.
 	Discrete,
 
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Character/Character.h b/thirdparty/jolt_physics/Jolt/Physics/Character/Character.h
index db67f8b3b6b7..cbf5d88dd515 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Character/Character.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/Character/Character.h
@@ -92,7 +92,7 @@ class JPH_EXPORT Character : public CharacterBase
 	RVec3								GetPosition(bool inLockBodies = true) const;
 
 	/// Set the position of the character, optionally activating it.
-	void								SetPosition(RVec3Arg inPostion, EActivation inActivationMode = EActivation::Activate, bool inLockBodies = true);
+	void								SetPosition(RVec3Arg inPosition, EActivation inActivationMode = EActivation::Activate, bool inLockBodies = true);
 
 	/// Get the rotation of the character
 	Quat								GetRotation(bool inLockBodies = true) const;
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/BroadPhase.h b/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/BroadPhase.h
index 8b6506e901d7..937f9f07f081 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/BroadPhase.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/BroadPhase.h
@@ -75,7 +75,7 @@ class JPH_EXPORT BroadPhase : public BroadPhaseQuery
 	virtual void		RemoveBodies(BodyID *ioBodies, int inNumber) = 0;
 
 	/// Call whenever the aabb of a body changes (can change order of ioBodies array)
-	/// inTakeLock should be false if we're between LockModifications/UnlockModificiations in which case care needs to be taken to not call this between UpdatePrepare/UpdateFinalize
+	/// inTakeLock should be false if we're between LockModifications/UnlockModifications, in which case care needs to be taken to not call this between UpdatePrepare/UpdateFinalize
 	virtual void		NotifyBodiesAABBChanged(BodyID *ioBodies, int inNumber, bool inTakeLock = true) = 0;
 
 	/// Call whenever the layer (and optionally the aabb as well) of a body changes (can change order of ioBodies array)
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp b/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp
index 717244c02381..3e11a90cbbc7 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/BroadPhaseQuadTree.cpp
@@ -350,7 +350,7 @@ void BroadPhaseQuadTree::NotifyBodiesAABBChanged(BodyID *ioBodies, int inNumber,
 		// Find first body with different layer
 		BodyID *b_mid = std::upper_bound(b_start, b_end, broadphase_layer, [tracking](BroadPhaseLayer::Type inLayer, BodyID inBodyID) { return inLayer < tracking[inBodyID.GetIndex()].mBroadPhaseLayer; });
 
-		// Nodify all bodies of the same layer changed
+		// Notify all bodies of the same layer changed
 		mLayers[broadphase_layer].NotifyBodiesAABBChanged(bodies, mTracking, b_start, int(b_mid - b_start));
 
 		// Repeat
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp b/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp
index cfcf8ba30091..bec025602981 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/BroadPhase/QuadTree.cpp
@@ -702,7 +702,7 @@ void QuadTree::WidenAndMarkNodeAndParentsChanged(uint32 inNodeIndex, const AABox
 
 bool QuadTree::TryInsertLeaf(TrackingVector &ioTracking, int inNodeIndex, NodeID inLeafID, const AABox &inLeafBounds, int inLeafNumBodies)
 {
-	// Tentively assign the node as parent
+	// Tentatively assign the node as parent
 	bool leaf_is_node = inLeafID.IsNode();
 	if (leaf_is_node)
 	{
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/CastSphereVsTriangles.cpp b/thirdparty/jolt_physics/Jolt/Physics/Collision/CastSphereVsTriangles.cpp
index 166883be8da3..69abfbaf3119 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/CastSphereVsTriangles.cpp
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/CastSphereVsTriangles.cpp
@@ -104,7 +104,7 @@ float CastSphereVsTriangles::RayCylinder(Vec3Arg inRayDirection, Vec3Arg inCylin
 	float c = axis_len_sq * (start.LengthSq() - Square(inRadius)) - Square(start_dot_axis);
 	float det = Square(b) - a * c; // normally 4 * a * c but since both a and c need to be divided by 2 we lose the 4
 	if (det < 0.0f)
-		return FLT_MAX; // No solution to quadractic equation
+		return FLT_MAX; // No solution to quadratic equation
 
 	// Solve fraction t where the ray hits the cylinder
 	float t = -(b + sqrt(det)) / a; // normally divided by 2 * a but since a should be divided by 2 we lose the 2
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/CollideShape.h b/thirdparty/jolt_physics/Jolt/Physics/Collision/CollideShape.h
index 2c5f8f217ffa..cfec81393c4d 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/CollideShape.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/CollideShape.h
@@ -59,7 +59,7 @@ class CollideShapeResult
 	Vec3						mContactPointOn1;			///< Contact point on the surface of shape 1 (in world space or relative to base offset)
 	Vec3						mContactPointOn2;			///< Contact point on the surface of shape 2 (in world space or relative to base offset). If the penetration depth is 0, this will be the same as mContactPointOn1.
 	Vec3						mPenetrationAxis;			///< Direction to move shape 2 out of collision along the shortest path (magnitude is meaningless, in world space). You can use -mPenetrationAxis.Normalized() as contact normal.
-	float						mPenetrationDepth;			///< Penetration depth (move shape 2 by this distance to resolve the collision)
+	float						mPenetrationDepth;			///< Penetration depth (move shape 2 by this distance to resolve the collision). If CollideShapeSettings::mMaxSeparationDistance > 0 this number can be negative to indicate that the objects are separated by -mPenetrationDepth. The contact points are the closest points in that case.
 	SubShapeID					mSubShapeID1;				///< Sub shape ID that identifies the face on shape 1
 	SubShapeID					mSubShapeID2;				///< Sub shape ID that identifies the face on shape 2
 	BodyID						mBodyID2;					///< BodyID to which shape 2 belongs to
@@ -95,7 +95,8 @@ class CollideShapeSettings : public CollideSettingsBase
 public:
 	JPH_OVERRIDE_NEW_DELETE
 
-	/// When > 0 contacts in the vicinity of the query shape can be found. All nearest contacts that are not further away than this distance will be found (unit: meter)
+	/// When > 0 contacts in the vicinity of the query shape can be found. All nearest contacts that are not further away than this distance will be found.
+	/// Note that in this case CollideShapeResult::mPenetrationDepth can become negative to indicate that objects are not overlapping. (unit: meter)
 	float						mMaxSeparationDistance		= 0.0f;
 
 	/// How backfacing triangles should be treated
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp
index 1a1126e19056..5f1dc651af23 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/ConvexHullShape.cpp
@@ -104,7 +104,7 @@ ConvexHullShape::ConvexHullShape(const ConvexHullShapeSettings &inSettings, Shap
 		{
 			Vec3 v3 = inSettings.mPoints[e->mStartIdx] - mCenterOfMass;
 
-			// Affine transform that transforms a unit tetrahedon (with vertices (0, 0, 0), (1, 0, 0), (0, 1, 0) and (0, 0, 1) to this tetrahedron
+			// Affine transform that transforms a unit tetrahedron (with vertices (0, 0, 0), (1, 0, 0), (0, 1, 0) and (0, 0, 1) to this tetrahedron
 			Mat44 a(Vec4(v1, 0), Vec4(v2, 0), Vec4(v3, 0), Vec4(0, 0, 0, 1));
 
 			// Calculate covariance matrix for this tetrahedron
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/Shape.h b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/Shape.h
index f43b79c3f0cc..4d9bb5354b85 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/Shape.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/Shape.h
@@ -331,7 +331,7 @@ class JPH_EXPORT Shape : public RefTarget<Shape>, public NonCopyable
 	/// Collect the leaf transformed shapes of all leaf shapes of this shape.
 	/// inBox is the world space axis aligned box which leaf shapes should collide with.
 	/// inPositionCOM/inRotation/inScale describes the transform of this shape.
-	/// inSubShapeIDCeator represents the current sub shape ID of this shape.
+	/// inSubShapeIDCreator represents the current sub shape ID of this shape.
 	virtual void					CollectTransformedShapes(const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale, const SubShapeIDCreator &inSubShapeIDCreator, TransformedShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) const;
 
 	/// Transforms this shape and all of its children with inTransform, resulting shape(s) are passed to ioCollector.
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/SubShapeIDPair.h b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/SubShapeIDPair.h
index f33ca310e70c..25e30c2b7750 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/SubShapeIDPair.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/SubShapeIDPair.h
@@ -62,19 +62,4 @@ static_assert(alignof(SubShapeIDPair) == 4, "Assuming 4 byte aligned");
 
 JPH_NAMESPACE_END
 
-JPH_SUPPRESS_WARNINGS_STD_BEGIN
-
-namespace std
-{
-	/// Declare std::hash for SubShapeIDPair
-	template <>
-	struct hash<JPH::SubShapeIDPair>
-	{
-		inline size_t operator () (const JPH::SubShapeIDPair &inRHS) const
-		{
-			return static_cast<size_t>(inRHS.GetHash());
-		}
-	};
-}
-
-JPH_SUPPRESS_WARNINGS_STD_END
+JPH_MAKE_STD_HASH(JPH::SubShapeIDPair)
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.cpp b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.cpp
index d817644fb44d..1bb9c58584d3 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.cpp
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/TaperedCylinderShape.cpp
@@ -313,7 +313,7 @@ MassProperties TaperedCylinderShape::GetMassProperties() const
 	// Ixx(br,tr,b,t):=integrate(dix(x)+area(x)*x^2,x,b,t)*density(b,t);
 	// Inertia tensor element yy:
 	// Iyy(br,tr,b,t):=integrate(diy(x),x,b,t)*density(b,t);
-	// Note that we can simplfy Ixx by using:
+	// Note that we can simplify Ixx by using:
 	// Ixx_delta(br,tr,b,t):=Ixx(br,tr,b,t)-Iyy(br,tr,b,t)/2;
 	// For a cylinder this formula matches what is listed on the wiki:
 	// factor(Ixx(r,r,-h/2,h/2));
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/TriangleShape.cpp b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/TriangleShape.cpp
index 5f2454bbb5bf..5bfa05b91056 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/TriangleShape.cpp
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/Shape/TriangleShape.cpp
@@ -91,7 +91,7 @@ class TriangleShape::TriangleNoConvex final : public Support
 {
 public:
 							TriangleNoConvex(Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3) :
-		mTriangleSuport(inV1, inV2, inV3)
+		mTriangleSupport(inV1, inV2, inV3)
 	{
 		static_assert(sizeof(TriangleNoConvex) <= sizeof(SupportBuffer), "Buffer size too small");
 		JPH_ASSERT(IsAligned(this, alignof(TriangleNoConvex)));
@@ -99,7 +99,7 @@ class TriangleShape::TriangleNoConvex final : public Support
 
 	virtual Vec3			GetSupport(Vec3Arg inDirection) const override
 	{
-		return mTriangleSuport.GetSupport(inDirection);
+		return mTriangleSupport.GetSupport(inDirection);
 	}
 
 	virtual float			GetConvexRadius() const override
@@ -108,7 +108,7 @@ class TriangleShape::TriangleNoConvex final : public Support
 	}
 
 private:
-	TriangleConvexSupport	mTriangleSuport;
+	TriangleConvexSupport	mTriangleSupport;
 };
 
 class TriangleShape::TriangleWithConvex final : public Support
@@ -116,7 +116,7 @@ class TriangleShape::TriangleWithConvex final : public Support
 public:
 							TriangleWithConvex(Vec3Arg inV1, Vec3Arg inV2, Vec3Arg inV3, float inConvexRadius) :
 		mConvexRadius(inConvexRadius),
-		mTriangleSuport(inV1, inV2, inV3)
+		mTriangleSupport(inV1, inV2, inV3)
 	{
 		static_assert(sizeof(TriangleWithConvex) <= sizeof(SupportBuffer), "Buffer size too small");
 		JPH_ASSERT(IsAligned(this, alignof(TriangleWithConvex)));
@@ -124,7 +124,7 @@ class TriangleShape::TriangleWithConvex final : public Support
 
 	virtual Vec3			GetSupport(Vec3Arg inDirection) const override
 	{
-		Vec3 support = mTriangleSuport.GetSupport(inDirection);
+		Vec3 support = mTriangleSupport.GetSupport(inDirection);
 		float len = inDirection.Length();
 		if (len > 0.0f)
 			support += (mConvexRadius / len) * inDirection;
@@ -138,7 +138,7 @@ class TriangleShape::TriangleWithConvex final : public Support
 
 private:
 	float					mConvexRadius;
-	TriangleConvexSupport	mTriangleSuport;
+	TriangleConvexSupport	mTriangleSupport;
 };
 
 const ConvexShape::Support *TriangleShape::GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Collision/TransformedShape.h b/thirdparty/jolt_physics/Jolt/Physics/Collision/TransformedShape.h
index 887a8ee44114..4c410b3b6d60 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Collision/TransformedShape.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/Collision/TransformedShape.h
@@ -89,7 +89,7 @@ class JPH_EXPORT TransformedShape
 	inline Vec3					GetShapeScale() const						{ return Vec3::sLoadFloat3Unsafe(mShapeScale); }
 	inline void					SetShapeScale(Vec3Arg inScale)				{ inScale.StoreFloat3(&mShapeScale); }
 
-	/// Calculates the transform for this shapes's center of mass (excluding scale)
+	/// Calculates the transform for this shape's center of mass (excluding scale)
 	inline RMat44				GetCenterOfMassTransform() const			{ return RMat44::sRotationTranslation(mShapeRotation, mShapePositionCOM); }
 
 	/// Calculates the inverse of the transform for this shape's center of mass (excluding scale)
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Constraints/ContactConstraintManager.cpp b/thirdparty/jolt_physics/Jolt/Physics/Constraints/ContactConstraintManager.cpp
index 928acdaa4221..bae886ca4c9f 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Constraints/ContactConstraintManager.cpp
+++ b/thirdparty/jolt_physics/Jolt/Physics/Constraints/ContactConstraintManager.cpp
@@ -136,7 +136,7 @@ JPH_INLINE void ContactConstraintManager::WorldContactPoint::TemplatedCalculateF
 		float surface_velocity1 = inWorldSpaceTangent1.Dot(ws_surface_velocity);
 		float surface_velocity2 = inWorldSpaceTangent2.Dot(ws_surface_velocity);
 
-		// Implement friction as 2 AxisContraintParts
+		// Implement friction as 2 AxisConstraintParts
 		mFrictionConstraint1.TemplatedCalculateConstraintProperties<Type1, Type2>(inInvM1, inInvI1, r1, inInvM2, inInvI2, r2, inWorldSpaceTangent1, surface_velocity1);
 		mFrictionConstraint2.TemplatedCalculateConstraintProperties<Type1, Type2>(inInvM1, inInvI1, r1, inInvM2, inInvI2, r2, inWorldSpaceTangent2, surface_velocity2);
 	}
diff --git a/thirdparty/jolt_physics/Jolt/Physics/PhysicsSettings.h b/thirdparty/jolt_physics/Jolt/Physics/PhysicsSettings.h
index eb8ecbbeaf81..0f2ed58ffe1a 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/PhysicsSettings.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/PhysicsSettings.h
@@ -80,13 +80,19 @@ struct PhysicsSettings
 	/// Number of solver position iterations to run
 	uint		mNumPositionSteps = 2;
 
-	/// Minimal velocity needed before a collision can be elastic (unit: m)
+	/// Minimal velocity needed before a collision can be elastic. If the relative velocity between colliding objects
+	/// in the direction of the contact normal is lower than this, the restitution will be zero regardless of the configured
+	/// value. This lets an object settle sooner. Must be a positive number. (unit: m)
 	float		mMinVelocityForRestitution = 1.0f;
 
 	/// Time before object is allowed to go to sleep (unit: seconds)
 	float		mTimeBeforeSleep = 0.5f;
 
-	/// Velocity of points on bounding box of object below which an object can be considered sleeping (unit: m/s)
+	/// To detect if an object is sleeping, we use 3 points:
+	/// - The center of mass.
+	/// - The centers of the faces of the bounding box that are furthest away from the center.
+	/// The movement of these points is tracked and if the velocity of all 3 points is lower than this value,
+	/// the object is allowed to go to sleep. Must be a positive number. (unit: m/s)
 	float		mPointVelocitySleepThreshold = 0.03f;
 
 	/// By default the simulation is deterministic, it is possible to turn this off by setting this setting to false. This will make the simulation run faster but it will no longer be deterministic.
diff --git a/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.cpp b/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.cpp
index 3ea6beabfab6..215693764929 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.cpp
+++ b/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.cpp
@@ -1443,7 +1443,7 @@ void PhysicsSystem::JobSolveVelocityConstraints(PhysicsUpdateContext *ioContext,
 		}
 		else if (check_split_islands)
 		{
-			// If there are split islands, but we did't do any work, give up a time slice
+			// If there are split islands, but we didn't do any work, give up a time slice
 			std::this_thread::yield();
 		}
 		else
@@ -2462,7 +2462,7 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext,
 		}
 		else if (check_split_islands)
 		{
-			// If there are split islands, but we did't do any work, give up a time slice
+			// If there are split islands, but we didn't do any work, give up a time slice
 			std::this_thread::yield();
 		}
 		else
diff --git a/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.h b/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.h
index 5b1d1acf54ca..b43d1e412320 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/PhysicsSystem.h
@@ -65,7 +65,7 @@ class JPH_EXPORT PhysicsSystem : public NonCopyable
 
 	/// Set the function that combines the restitution of two bodies and returns it
 	/// Default method is max(restitution1, restitution1)
-	void						SetCombineRestitution(ContactConstraintManager::CombineFunction inCombineRestition) { mContactManager.SetCombineRestitution(inCombineRestition); }
+	void						SetCombineRestitution(ContactConstraintManager::CombineFunction inCombineRestitution) { mContactManager.SetCombineRestitution(inCombineRestitution); }
 	ContactConstraintManager::CombineFunction GetCombineRestitution() const					{ return mContactManager.GetCombineRestitution(); }
 
 	/// Set/get the shape filter that will be used during simulation. This can be used to exclude shapes within a body from colliding with each other.
@@ -126,6 +126,8 @@ class JPH_EXPORT PhysicsSystem : public NonCopyable
 	/// The world steps for a total of inDeltaTime seconds. This is divided in inCollisionSteps iterations.
 	/// Each iteration consists of collision detection followed by an integration step.
 	/// This function internally spawns jobs using inJobSystem and waits for them to complete, so no jobs will be running when this function returns.
+	/// The temp allocator is used, for example, to store the list of bodies that are in contact, how they form islands together
+	/// and data to solve the contacts between bodies. At the end of the Update call, all allocated memory will have been freed.
 	EPhysicsUpdateError			Update(float inDeltaTime, int inCollisionSteps, TempAllocator *inTempAllocator, JobSystem *inJobSystem);
 
 	/// Saving state for replay
diff --git a/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp b/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp
index 0aad94de88c8..78eacd485a96 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp
+++ b/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp
@@ -599,7 +599,7 @@ void SoftBodyMotionProperties::ApplyCollisionConstraintsAndUpdateVelocities(cons
 	JPH_PROFILE_FUNCTION();
 
 	float dt = inContext.mSubStepDeltaTime;
-	float restitution_treshold = -2.0f * inContext.mGravity.Length() * dt;
+	float restitution_threshold = -2.0f * inContext.mGravity.Length() * dt;
 	float vertex_radius = mSettings->mVertexRadius;
 	for (Vertex &v : mVertices)
 		if (v.mInvMass > 0.0f)
@@ -674,7 +674,7 @@ void SoftBodyMotionProperties::ApplyCollisionConstraintsAndUpdateVelocities(cons
 							// Calculate delta relative velocity due to restitution (equation 35)
 							dv += v_normal;
 							float prev_v_normal = (prev_v - v2).Dot(contact_normal);
-							if (prev_v_normal < restitution_treshold)
+							if (prev_v_normal < restitution_threshold)
 								dv += cs.mRestitution * prev_v_normal * contact_normal;
 
 							// Calculate impulse
@@ -707,7 +707,7 @@ void SoftBodyMotionProperties::ApplyCollisionConstraintsAndUpdateVelocities(cons
 						// Apply restitution (equation 35)
 						v.mVelocity -= v_normal;
 						float prev_v_normal = prev_v.Dot(contact_normal);
-						if (prev_v_normal < restitution_treshold)
+						if (prev_v_normal < restitution_threshold)
 							v.mVelocity -= cs.mRestitution * prev_v_normal * contact_normal;
 					}
 				}
@@ -1032,7 +1032,10 @@ void SoftBodyMotionProperties::SkinVertices([[maybe_unused]] RMat44Arg inCenterO
 	const Mat44 *skin_matrices_end = skin_matrices + num_skin_matrices;
 	const InvBind *inv_bind_matrix = mSettings->mInvBindMatrices.data();
 	for (Mat44 *s = skin_matrices; s < skin_matrices_end; ++s, ++inv_bind_matrix)
+	{
+		JPH_ASSERT(inv_bind_matrix->mJointIndex < inNumJoints);
 		*s = inJointMatrices[inv_bind_matrix->mJointIndex] * inv_bind_matrix->mInvBind;
+	}
 
 	// Skin the vertices
 	JPH_IF_DEBUG_RENDERER(mSkinStateTransform = inCenterOfMassTransform;)
diff --git a/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h b/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h
index af66b7a2e4a9..8f53ced05aac 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodyMotionProperties.h
@@ -125,7 +125,7 @@ class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties
 	/// This function allows you to update the soft body immediately without going through the PhysicsSystem.
 	/// This is useful if the soft body is teleported and needs to 'settle' or it can be used if a the soft body
 	/// is not added to the PhysicsSystem and needs to be updated manually. One reason for not adding it to the
-	/// PhyicsSystem is that you might want to update a soft body immediately after updating an animated object
+	/// PhysicsSystem is that you might want to update a soft body immediately after updating an animated object
 	/// that has the soft body attached to it. If the soft body is added to the PhysicsSystem it will be updated
 	/// by it, so calling this function will effectively update it twice. Note that when you use this function,
 	/// only the current thread will be used, whereas if you update through the PhysicsSystem, multiple threads may
diff --git a/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodySharedSettings.h b/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodySharedSettings.h
index d4b05a6ccdae..4d7729bcedab 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodySharedSettings.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/SoftBody/SoftBodySharedSettings.h
@@ -204,7 +204,7 @@ class JPH_EXPORT SoftBodySharedSettings : public RefTarget<SoftBodySharedSetting
 		/// Return the lowest vertex index of this constraint
 		uint32			GetMinVertexIndex() const					{ return min(min(mVertex[0], mVertex[1]), min(mVertex[2], mVertex[3])); }
 
-		uint32			mVertex[4];									///< Indices of the vertices that form the tetrhedron
+		uint32			mVertex[4];									///< Indices of the vertices that form the tetrahedron
 		float			mSixRestVolume = 1.0f;						///< 6 times the rest volume of the tetrahedron (calculated by CalculateVolumeConstraintVolumes())
 		float			mCompliance = 0.0f;							///< Inverse of the stiffness of the constraint
 	};
diff --git a/thirdparty/jolt_physics/Jolt/Physics/Vehicle/TrackedVehicleController.h b/thirdparty/jolt_physics/Jolt/Physics/Vehicle/TrackedVehicleController.h
index 8567c97073db..0bca9de5cd33 100644
--- a/thirdparty/jolt_physics/Jolt/Physics/Vehicle/TrackedVehicleController.h
+++ b/thirdparty/jolt_physics/Jolt/Physics/Vehicle/TrackedVehicleController.h
@@ -25,7 +25,7 @@ class JPH_EXPORT WheelSettingsTV : public WheelSettings
 	virtual void				RestoreBinaryState(StreamIn &inStream) override;
 
 	float						mLongitudinalFriction = 4.0f;				///< Friction in forward direction of tire
-	float						mLateralFriction = 2.0f;					///< Friction in sideway direction of tire
+	float						mLateralFriction = 2.0f;					///< Friction in sideways direction of tire
 };
 
 /// Wheel object specifically for TrackedVehicleController
diff --git a/thirdparty/jolt_physics/Jolt/Skeleton/SkeletalAnimation.cpp b/thirdparty/jolt_physics/Jolt/Skeleton/SkeletalAnimation.cpp
index 4bf87007e9a8..27ff583f7280 100644
--- a/thirdparty/jolt_physics/Jolt/Skeleton/SkeletalAnimation.cpp
+++ b/thirdparty/jolt_physics/Jolt/Skeleton/SkeletalAnimation.cpp
@@ -7,6 +7,8 @@
 #include <Jolt/Skeleton/SkeletalAnimation.h>
 #include <Jolt/Skeleton/SkeletonPose.h>
 #include <Jolt/ObjectStream/TypeDeclarations.h>
+#include <Jolt/Core/StreamIn.h>
+#include <Jolt/Core/StreamOut.h>
 
 JPH_NAMESPACE_BEGIN
 
@@ -107,4 +109,57 @@ void SkeletalAnimation::Sample(float inTime, SkeletonPose &ioPose) const
 	}
 }
 
+void SkeletalAnimation::SaveBinaryState(StreamOut &inStream) const
+{
+	inStream.Write((uint32)mAnimatedJoints.size());
+	for (const AnimatedJoint &j : mAnimatedJoints)
+	{
+		// Write Joint name and number of keyframes
+		inStream.Write(j.mJointName);
+		inStream.Write((uint32)j.mKeyframes.size());
+		for (const Keyframe &k : j.mKeyframes)
+		{
+			inStream.Write(k.mTime);
+			inStream.Write(k.mRotation);
+			inStream.Write(k.mTranslation);
+		}
+	}
+
+	// Save additional parameters
+	inStream.Write(mIsLooping);
+}
+
+SkeletalAnimation::AnimationResult SkeletalAnimation::sRestoreFromBinaryState(StreamIn &inStream)
+{
+	AnimationResult result;
+
+	Ref<SkeletalAnimation> animation = new SkeletalAnimation;
+
+	// Restore animated joints
+	uint32 len = 0;
+	inStream.Read(len);
+	animation->mAnimatedJoints.resize(len);
+	for (AnimatedJoint &j : animation->mAnimatedJoints)
+	{
+		// Read joint name
+		inStream.Read(j.mJointName);
+
+		// Read keyframes
+		len = 0;
+		inStream.Read(len);
+		j.mKeyframes.resize(len);
+		for (Keyframe &k : j.mKeyframes)
+		{
+			inStream.Read(k.mTime);
+			inStream.Read(k.mRotation);
+			inStream.Read(k.mTranslation);
+		}
+	}
+
+	// Read additional parameters
+	inStream.Read(animation->mIsLooping);
+	result.Set(animation);
+	return result;
+}
+
 JPH_NAMESPACE_END
diff --git a/thirdparty/jolt_physics/Jolt/Skeleton/SkeletalAnimation.h b/thirdparty/jolt_physics/Jolt/Skeleton/SkeletalAnimation.h
index 344c6046fa10..a5769f14c3c2 100644
--- a/thirdparty/jolt_physics/Jolt/Skeleton/SkeletalAnimation.h
+++ b/thirdparty/jolt_physics/Jolt/Skeleton/SkeletalAnimation.h
@@ -5,6 +5,8 @@
 #pragma once
 
 #include <Jolt/Core/Reference.h>
+#include <Jolt/Core/Result.h>
+#include <Jolt/Core/StreamUtils.h>
 #include <Jolt/ObjectStream/SerializableObject.h>
 
 JPH_NAMESPACE_BEGIN
@@ -62,6 +64,10 @@ class JPH_EXPORT SkeletalAnimation : public RefTarget<SkeletalAnimation>
 	/// Scale the size of all joints by inScale
 	void								ScaleJoints(float inScale);
 
+	/// If the animation is looping or not. If an animation is looping, the animation will continue playing after completion
+	void 								SetIsLooping(bool inIsLooping)						{ mIsLooping = inIsLooping; }
+	bool								IsLooping() const									{ return mIsLooping; }
+
 	/// Get the (interpolated) joint transforms at time inTime
 	void								Sample(float inTime, SkeletonPose &ioPose) const;
 
@@ -69,6 +75,14 @@ class JPH_EXPORT SkeletalAnimation : public RefTarget<SkeletalAnimation>
 	const AnimatedJointVector &			GetAnimatedJoints() const							{ return mAnimatedJoints; }
 	AnimatedJointVector &				GetAnimatedJoints()									{ return mAnimatedJoints; }
 
+	/// Saves the state of this animation in binary form to inStream.
+	void								SaveBinaryState(StreamOut &inStream) const;
+
+	using AnimationResult = Result<Ref<SkeletalAnimation>>;
+
+	/// Restore a saved ragdoll from inStream
+	static AnimationResult				sRestoreFromBinaryState(StreamIn &inStream);
+
 private:
 	AnimatedJointVector					mAnimatedJoints;									///< List of joints and keyframes
 	bool								mIsLooping = true;									///< If this animation loops back to start


Test patch: