浏览代码

Ability to add soft bodies to a PhysicsScene and load/save them (#656)

Jorrit Rouwe 2 年之前
父节点
当前提交
779ba3673b

+ 10 - 10
Jolt/Core/Result.h

@@ -16,7 +16,7 @@ class Result
 public:
 public:
 	/// Default constructor
 	/// Default constructor
 						Result()									{ }
 						Result()									{ }
-						
+
 	/// Copy constructor
 	/// Copy constructor
 						Result(const Result<Type> &inRHS) :
 						Result(const Result<Type> &inRHS) :
 		mState(inRHS.mState)
 		mState(inRHS.mState)
@@ -54,7 +54,7 @@ public:
 			break;
 			break;
 		}
 		}
 
 
-		inRHS.mState = EState::Invalid;
+		// Don't reset the state of inRHS, the destructors still need to be called after a move operation
 	}
 	}
 
 
 	/// Destructor
 	/// Destructor
@@ -105,19 +105,19 @@ public:
 			break;
 			break;
 		}
 		}
 
 
-		inRHS.mState = EState::Invalid;
+		// Don't reset the state of inRHS, the destructors still need to be called after a move operation
 
 
 		return *this;
 		return *this;
 	}
 	}
 
 
 	/// Clear result or error
 	/// Clear result or error
 	void				Clear()
 	void				Clear()
-	{ 
-		switch (mState) 
-		{ 
-		case EState::Valid: 
-			mResult.~Type(); 
-			break; 
+	{
+		switch (mState)
+		{
+		case EState::Valid:
+			mResult.~Type();
+			break;
 
 
 		case EState::Error:
 		case EState::Error:
 			mError.~String();
 			mError.~String();
@@ -143,7 +143,7 @@ public:
 	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)
 	/// Set the result value (move value)
-	void				Set(const 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
 	/// Check if we had an error
 	bool				HasError() const							{ return mState == EState::Error; }
 	bool				HasError() const							{ return mState == EState::Error; }

+ 167 - 0
Jolt/Core/StreamUtils.h

@@ -0,0 +1,167 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Jolt/Core/Result.h>
+#include <Jolt/Core/StreamIn.h>
+#include <Jolt/Core/StreamOut.h>
+#include <Jolt/Core/UnorderedMap.h>
+#include <Jolt/Core/Factory.h>
+
+JPH_NAMESPACE_BEGIN
+
+namespace StreamUtils {
+
+template <class Type>
+using ObjectToIDMap = UnorderedMap<const Type *, uint32>;
+
+template <class Type>
+using IDToObjectMap = Array<Ref<Type>>;
+
+// Restore a single object by reading the hash of the type, constructing it and then calling the restore function
+template <class Type>
+Result<Ref<Type>>	RestoreObject(StreamIn &inStream, void (Type::*inRestoreBinaryStateFunction)(StreamIn &))
+{
+	Result<Ref<Type>> result;
+
+	// Read the hash of the type
+	uint32 hash;
+	inStream.Read(hash);
+	if (inStream.IsEOF() || inStream.IsFailed())
+	{
+		result.SetError("Failed to read type hash");
+		return result;
+	}
+
+	// Get the RTTI for the type
+	const RTTI *rtti = Factory::sInstance->Find(hash);
+	if (rtti == nullptr)
+	{
+		result.SetError("Failed to create instance of type");
+		return result;
+	}
+
+	// Construct and read the data of the type
+	Ref<Type> object = reinterpret_cast<Type *>(rtti->CreateObject());
+	(object->*inRestoreBinaryStateFunction)(inStream);
+	if (inStream.IsEOF() || inStream.IsFailed())
+	{
+		result.SetError("Failed to restore object");
+		return result;
+	}
+
+	result.Set(object);
+	return result;
+}
+
+/// Save an object reference to a stream. Uses a map to map objects to IDs which is also used to prevent writing duplicates.
+template <class Type>
+void				SaveObjectReference(StreamOut &inStream, const Type *inObject, ObjectToIDMap<Type> *ioObjectToIDMap)
+{
+	if (ioObjectToIDMap == nullptr || inObject == nullptr)
+	{
+		// Write null ID
+		inStream.Write(~uint32(0));
+	}
+	else
+	{
+		typename ObjectToIDMap<Type>::const_iterator id = ioObjectToIDMap->find(inObject);
+		if (id != ioObjectToIDMap->end())
+		{
+			// Existing object, write ID
+			inStream.Write(id->second);
+		}
+		else
+		{
+			// New object, write the ID
+			uint32 new_id = (uint32)ioObjectToIDMap->size();
+			(*ioObjectToIDMap)[inObject] = new_id;
+			inStream.Write(new_id);
+
+			// Write the object
+			inObject->SaveBinaryState(inStream);
+		}
+	}
+}
+
+/// Restore an object reference from stream.
+template <class Type>
+Result<Ref<Type>>	RestoreObjectReference(StreamIn &inStream, IDToObjectMap<Type> &ioIDToObjectMap)
+{
+	Result<Ref<Type>> result;
+
+	// Read id
+	uint32 id = ~uint32(0);
+	inStream.Read(id);
+
+	// Check null
+	if (id == ~uint32(0))
+	{
+		result.Set(nullptr);
+		return result;
+	}
+
+	// Check if it already exists
+	if (id >= ioIDToObjectMap.size())
+	{
+		// New object, restore it
+		result = Type::sRestoreFromBinaryState(inStream);
+		if (result.HasError())
+			return result;
+		JPH_ASSERT(id == ioIDToObjectMap.size());
+		ioIDToObjectMap.push_back(result.Get());
+	}
+	else
+	{
+		// Existing object filter
+		result.Set(ioIDToObjectMap[id].GetPtr());
+	}
+
+	return result;
+}
+
+// Save an array of objects to a stream.
+template <class ArrayType, class ValueType>
+void				SaveObjectArray(StreamOut &inStream, const ArrayType &inArray, ObjectToIDMap<ValueType> *ioObjectToIDMap)
+{
+	inStream.Write((size_t)inArray.size());
+	for (const ValueType *value: inArray)
+		SaveObjectReference(inStream, value, ioObjectToIDMap);
+}
+
+// Restore an array of objects from a stream.
+template <class ArrayType, class ValueType>
+Result<ArrayType>	RestoreObjectArray(StreamIn &inStream, IDToObjectMap<ValueType> &ioIDToObjectMap)
+{
+	Result<ArrayType> result;
+
+	size_t len;
+	inStream.Read(len);
+	if (inStream.IsEOF() || inStream.IsFailed())
+	{
+		result.SetError("Failed to read stream");
+		return result;
+	}
+
+	ArrayType values;
+	values.reserve(len);
+	for (size_t i = 0; i < len; ++i)
+	{
+		Result value = RestoreObjectReference(inStream, ioIDToObjectMap);
+		if (value.HasError())
+		{
+			result.SetError(value.GetError());
+			return result;
+		}
+		values.push_back(std::move(value.Get()));
+	}
+
+	result.Set(values);
+	return result;
+}
+
+} // StreamUtils
+
+JPH_NAMESPACE_END

+ 1 - 0
Jolt/Jolt.cmake

@@ -60,6 +60,7 @@ set(JOLT_PHYSICS_SRC_FILES
 	${JOLT_PHYSICS_ROOT}/Core/StaticArray.h
 	${JOLT_PHYSICS_ROOT}/Core/StaticArray.h
 	${JOLT_PHYSICS_ROOT}/Core/StreamIn.h
 	${JOLT_PHYSICS_ROOT}/Core/StreamIn.h
 	${JOLT_PHYSICS_ROOT}/Core/StreamOut.h
 	${JOLT_PHYSICS_ROOT}/Core/StreamOut.h
+	${JOLT_PHYSICS_ROOT}/Core/StreamUtils.h
 	${JOLT_PHYSICS_ROOT}/Core/StreamWrapper.h
 	${JOLT_PHYSICS_ROOT}/Core/StreamWrapper.h
 	${JOLT_PHYSICS_ROOT}/Core/StringTools.cpp
 	${JOLT_PHYSICS_ROOT}/Core/StringTools.cpp
 	${JOLT_PHYSICS_ROOT}/Core/StringTools.h
 	${JOLT_PHYSICS_ROOT}/Core/StringTools.h

+ 31 - 29
Jolt/ObjectStream/ObjectStream.h

@@ -119,21 +119,21 @@ public:
 
 
 // Define serialization templates
 // Define serialization templates
 template <class T>
 template <class T>
-bool OSIsType(Array<T> *, int inArrayDepth, EOSDataType inDataType, const char *inClassName)	
-{ 
-	return (inArrayDepth > 0 && OSIsType((T *)nullptr, inArrayDepth - 1, inDataType, inClassName)); 
+bool OSIsType(Array<T> *, int inArrayDepth, EOSDataType inDataType, const char *inClassName)
+{
+	return (inArrayDepth > 0 && OSIsType((T *)nullptr, inArrayDepth - 1, inDataType, inClassName));
 }
 }
 
 
 template <class T, uint N>
 template <class T, uint N>
-bool OSIsType(StaticArray<T, N> *, int inArrayDepth, EOSDataType inDataType, const char *inClassName)	
-{ 
-	return (inArrayDepth > 0 && OSIsType((T *)nullptr, inArrayDepth - 1, inDataType, inClassName)); 
+bool OSIsType(StaticArray<T, N> *, int inArrayDepth, EOSDataType inDataType, const char *inClassName)
+{
+	return (inArrayDepth > 0 && OSIsType((T *)nullptr, inArrayDepth - 1, inDataType, inClassName));
 }
 }
 
 
 template <class T, uint N>
 template <class T, uint N>
-bool OSIsType(T (*)[N], int inArrayDepth, EOSDataType inDataType, const char *inClassName)	
-{ 
-	return (inArrayDepth > 0 && OSIsType((T *)nullptr, inArrayDepth - 1, inDataType, inClassName)); 
+bool OSIsType(T (*)[N], int inArrayDepth, EOSDataType inDataType, const char *inClassName)
+{
+	return (inArrayDepth > 0 && OSIsType((T *)nullptr, inArrayDepth - 1, inDataType, inClassName));
 }
 }
 
 
 template <class T>
 template <class T>
@@ -159,10 +159,11 @@ bool OSReadData(IObjectStreamIn &ioStream, Array<T> &inArray)
 	continue_reading = ioStream.ReadCount(array_length);
 	continue_reading = ioStream.ReadCount(array_length);
 
 
 	// Read array items
 	// Read array items
-	if (continue_reading) 
+	if (continue_reading)
 	{
 	{
+		inArray.clear();
 		inArray.resize(array_length);
 		inArray.resize(array_length);
-		for (uint32 el = 0; el < array_length && continue_reading; ++el) 
+		for (uint32 el = 0; el < array_length && continue_reading; ++el)
 			continue_reading = OSReadData(ioStream, inArray[el]);
 			continue_reading = OSReadData(ioStream, inArray[el]);
 	}
 	}
 
 
@@ -184,10 +185,11 @@ bool OSReadData(IObjectStreamIn &ioStream, StaticArray<T, N> &inArray)
 		return false;
 		return false;
 
 
 	// Read array items
 	// Read array items
-	if (continue_reading) 
+	if (continue_reading)
 	{
 	{
+		inArray.clear();
 		inArray.resize(array_length);
 		inArray.resize(array_length);
-		for (uint32 el = 0; el < array_length && continue_reading; ++el) 
+		for (uint32 el = 0; el < array_length && continue_reading; ++el)
 			continue_reading = OSReadData(ioStream, inArray[el]);
 			continue_reading = OSReadData(ioStream, inArray[el]);
 	}
 	}
 
 
@@ -207,7 +209,7 @@ bool OSReadData(IObjectStreamIn &ioStream, T (&inArray)[N])
 		return false;
 		return false;
 
 
 	// Read array items
 	// Read array items
-	for (uint32 el = 0; el < N && continue_reading; ++el) 
+	for (uint32 el = 0; el < N && continue_reading; ++el)
 		continue_reading = OSReadData(ioStream, inArray[el]);
 		continue_reading = OSReadData(ioStream, inArray[el]);
 
 
 	return continue_reading;
 	return continue_reading;
@@ -228,10 +230,10 @@ bool OSReadData(IObjectStreamIn &ioStream, RefConst<T> &inRef)
 
 
 // Define serialization templates for dynamic arrays
 // Define serialization templates for dynamic arrays
 template <class T>
 template <class T>
-void OSWriteDataType(IObjectStreamOut &ioStream, Array<T> *)		
-{ 
-	ioStream.WriteDataType(EOSDataType::Array); 
-	OSWriteDataType(ioStream, (T *)nullptr); 
+void OSWriteDataType(IObjectStreamOut &ioStream, Array<T> *)
+{
+	ioStream.WriteDataType(EOSDataType::Array);
+	OSWriteDataType(ioStream, (T *)nullptr);
 }
 }
 
 
 template <class T>
 template <class T>
@@ -242,7 +244,7 @@ void OSWriteData(IObjectStreamOut &ioStream, const Array<T> &inArray)
 	ioStream.WriteCount((uint32)inArray.size());
 	ioStream.WriteCount((uint32)inArray.size());
 
 
 	// Write data in array
 	// Write data in array
-	ioStream.HintIndentUp();	
+	ioStream.HintIndentUp();
 	for (const T &v : inArray)
 	for (const T &v : inArray)
 		OSWriteData(ioStream, v);
 		OSWriteData(ioStream, v);
 	ioStream.HintIndentDown();
 	ioStream.HintIndentDown();
@@ -250,10 +252,10 @@ void OSWriteData(IObjectStreamOut &ioStream, const Array<T> &inArray)
 
 
 /// Define serialization templates for static arrays
 /// Define serialization templates for static arrays
 template <class T, uint N>
 template <class T, uint N>
-void OSWriteDataType(IObjectStreamOut &ioStream, StaticArray<T, N> *)		
-{ 
-	ioStream.WriteDataType(EOSDataType::Array); 
-	OSWriteDataType(ioStream, (T *)nullptr); 
+void OSWriteDataType(IObjectStreamOut &ioStream, StaticArray<T, N> *)
+{
+	ioStream.WriteDataType(EOSDataType::Array);
+	OSWriteDataType(ioStream, (T *)nullptr);
 }
 }
 
 
 template <class T, uint N>
 template <class T, uint N>
@@ -264,7 +266,7 @@ void OSWriteData(IObjectStreamOut &ioStream, const StaticArray<T, N> &inArray)
 	ioStream.WriteCount(inArray.size());
 	ioStream.WriteCount(inArray.size());
 
 
 	// Write data in array
 	// Write data in array
-	ioStream.HintIndentUp();	
+	ioStream.HintIndentUp();
 	for (const typename StaticArray<T, N>::value_type &v : inArray)
 	for (const typename StaticArray<T, N>::value_type &v : inArray)
 		OSWriteData(ioStream, v);
 		OSWriteData(ioStream, v);
 	ioStream.HintIndentDown();
 	ioStream.HintIndentDown();
@@ -272,10 +274,10 @@ void OSWriteData(IObjectStreamOut &ioStream, const StaticArray<T, N> &inArray)
 
 
 /// Define serialization templates for C style arrays
 /// Define serialization templates for C style arrays
 template <class T, uint N>
 template <class T, uint N>
-void OSWriteDataType(IObjectStreamOut &ioStream, T (*)[N])		
-{ 
-	ioStream.WriteDataType(EOSDataType::Array); 
-	OSWriteDataType(ioStream, (T *)nullptr); 
+void OSWriteDataType(IObjectStreamOut &ioStream, T (*)[N])
+{
+	ioStream.WriteDataType(EOSDataType::Array);
+	OSWriteDataType(ioStream, (T *)nullptr);
 }
 }
 
 
 template <class T, uint N>
 template <class T, uint N>
@@ -286,7 +288,7 @@ void OSWriteData(IObjectStreamOut &ioStream, const T (&inArray)[N])
 	ioStream.WriteCount((uint32)N);
 	ioStream.WriteCount((uint32)N);
 
 
 	// Write data in array
 	// Write data in array
-	ioStream.HintIndentUp();	
+	ioStream.HintIndentUp();
 	for (const T &v : inArray)
 	for (const T &v : inArray)
 		OSWriteData(ioStream, v);
 		OSWriteData(ioStream, v);
 	ioStream.HintIndentDown();
 	ioStream.HintIndentDown();

+ 25 - 0
Jolt/Physics/Body/Body.cpp

@@ -6,6 +6,7 @@
 
 
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/Physics/Body/Body.h>
 #include <Jolt/Physics/Body/BodyCreationSettings.h>
 #include <Jolt/Physics/Body/BodyCreationSettings.h>
+#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
 #include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
 #include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
 #include <Jolt/Physics/PhysicsSettings.h>
 #include <Jolt/Physics/PhysicsSettings.h>
 #include <Jolt/Physics/StateRecorder.h>
 #include <Jolt/Physics/StateRecorder.h>
@@ -353,4 +354,28 @@ BodyCreationSettings Body::GetBodyCreationSettings() const
 	return result;
 	return result;
 }
 }
 
 
+SoftBodyCreationSettings Body::GetSoftBodyCreationSettings() const
+{
+	JPH_ASSERT(IsSoftBody());
+
+	SoftBodyCreationSettings result;
+
+	result.mPosition = GetPosition();
+	result.mRotation = GetRotation();
+	result.mUserData = mUserData;
+	result.mObjectLayer = GetObjectLayer();
+	result.mCollisionGroup = GetCollisionGroup();
+	result.mFriction = GetFriction();
+	result.mRestitution = GetRestitution();
+	const SoftBodyMotionProperties *mp = static_cast<const SoftBodyMotionProperties *>(mMotionProperties);
+	result.mNumIterations = mp->GetNumIterations();
+	result.mLinearDamping = mp->GetLinearDamping();
+	result.mGravityFactor = mp->GetGravityFactor();
+	result.mPressure = mp->GetPressure();
+	result.mUpdatePosition = mp->GetUpdatePosition();
+	result.mSettings = mp->GetSettings();
+
+	return result;
+}
+
 JPH_NAMESPACE_END
 JPH_NAMESPACE_END

+ 4 - 0
Jolt/Physics/Body/Body.h

@@ -21,6 +21,7 @@ JPH_NAMESPACE_BEGIN
 
 
 class StateRecorder;
 class StateRecorder;
 class BodyCreationSettings;
 class BodyCreationSettings;
+class SoftBodyCreationSettings;
 
 
 /// A rigid body that can be simulated using the physics system
 /// A rigid body that can be simulated using the physics system
 ///
 ///
@@ -244,6 +245,9 @@ public:
 	/// Debug function to convert a body back to a body creation settings object to be able to save/recreate the body later
 	/// Debug function to convert a body back to a body creation settings object to be able to save/recreate the body later
 	BodyCreationSettings	GetBodyCreationSettings() const;
 	BodyCreationSettings	GetBodyCreationSettings() const;
 
 
+	/// Debug function to convert a soft body back to a soft body creation settings object to be able to save/recreate the body later
+	SoftBodyCreationSettings GetSoftBodyCreationSettings() const;
+
 	/// A dummy body that can be used by constraints to attach a constraint to the world instead of another body
 	/// A dummy body that can be used by constraints to attach a constraint to the world instead of another body
 	static Body				sFixedToWorld;
 	static Body				sFixedToWorld;
 
 

+ 10 - 55
Jolt/Physics/Body/BodyCreationSettings.cpp

@@ -97,7 +97,7 @@ void BodyCreationSettings::RestoreBinaryState(StreamIn &inStream)
 	mMassPropertiesOverride.RestoreBinaryState(inStream);
 	mMassPropertiesOverride.RestoreBinaryState(inStream);
 }
 }
 
 
-Shape::ShapeResult BodyCreationSettings::ConvertShapeSettings() 
+Shape::ShapeResult BodyCreationSettings::ConvertShapeSettings()
 {
 {
 	// If we already have a shape, return it
 	// If we already have a shape, return it
 	if (mShapePtr != nullptr)
 	if (mShapePtr != nullptr)
@@ -125,8 +125,8 @@ Shape::ShapeResult BodyCreationSettings::ConvertShapeSettings()
 	return result;
 	return result;
 }
 }
 
 
-const Shape *BodyCreationSettings::GetShape() const												
-{ 
+const Shape *BodyCreationSettings::GetShape() const
+{
 	// If we already have a shape, return it
 	// If we already have a shape, return it
 	if (mShapePtr != nullptr)
 	if (mShapePtr != nullptr)
 		return mShapePtr;
 		return mShapePtr;
@@ -142,7 +142,7 @@ const Shape *BodyCreationSettings::GetShape() const
 
 
 	Trace("Error: %s", result.GetError().c_str());
 	Trace("Error: %s", result.GetError().c_str());
 	JPH_ASSERT(false, "An error occurred during shape creation. Use ConvertShapeSettings() to convert the shape and get the error!");
 	JPH_ASSERT(false, "An error occurred during shape creation. Use ConvertShapeSettings() to convert the shape and get the error!");
-	return nullptr;		
+	return nullptr;
 }
 }
 
 
 MassProperties BodyCreationSettings::GetMassProperties() const
 MassProperties BodyCreationSettings::GetMassProperties() const
@@ -181,31 +181,7 @@ void BodyCreationSettings::SaveWithChildren(StreamOut &inStream, ShapeToIDMap *i
 		inStream.Write(~uint32(0));
 		inStream.Write(~uint32(0));
 
 
 	// Save group filter
 	// Save group filter
-	const GroupFilter *group_filter = mCollisionGroup.GetGroupFilter();
-	if (ioGroupFilterMap == nullptr || group_filter == nullptr)
-	{
-		// Write null ID
-		inStream.Write(~uint32(0));
-	}
-	else
-	{
-		GroupFilterToIDMap::const_iterator group_filter_id = ioGroupFilterMap->find(group_filter);
-		if (group_filter_id != ioGroupFilterMap->end())
-		{
-			// Existing group filter, write ID
-			inStream.Write(group_filter_id->second);
-		}
-		else
-		{
-			// New group filter, write the ID
-			uint32 new_group_filter_id = (uint32)ioGroupFilterMap->size();
-			(*ioGroupFilterMap)[group_filter] = new_group_filter_id;
-			inStream.Write(new_group_filter_id);
-
-			// Write the group filter
-			group_filter->SaveBinaryState(inStream);
-		}
-	}
+	StreamUtils::SaveObjectReference(inStream, mCollisionGroup.GetGroupFilter(), ioGroupFilterMap);
 }
 }
 
 
 BodyCreationSettings::BCSResult BodyCreationSettings::sRestoreWithChildren(StreamIn &inStream, IDToShapeMap &ioShapeMap, IDToMaterialMap &ioMaterialMap, IDToGroupFilterMap &ioGroupFilterMap)
 BodyCreationSettings::BCSResult BodyCreationSettings::sRestoreWithChildren(StreamIn &inStream, IDToShapeMap &ioShapeMap, IDToMaterialMap &ioMaterialMap, IDToGroupFilterMap &ioGroupFilterMap)
@@ -231,34 +207,13 @@ BodyCreationSettings::BCSResult BodyCreationSettings::sRestoreWithChildren(Strea
 	settings.SetShape(shape_result.Get());
 	settings.SetShape(shape_result.Get());
 
 
 	// Read group filter
 	// Read group filter
-	const GroupFilter *group_filter = nullptr;
-	uint32 group_filter_id = ~uint32(0);
-	inStream.Read(group_filter_id);
-	if (group_filter_id != ~uint32(0))
+	Result gfresult = StreamUtils::RestoreObjectReference(inStream, ioGroupFilterMap);
+	if (gfresult.HasError())
 	{
 	{
-		// Check if it already exists
-		if (group_filter_id >= ioGroupFilterMap.size())
-		{
-			// New group filter, restore it
-			GroupFilter::GroupFilterResult group_filter_result = GroupFilter::sRestoreFromBinaryState(inStream);
-			if (group_filter_result.HasError())
-			{
-				result.SetError(group_filter_result.GetError());
-				return result;
-			}
-			group_filter = group_filter_result.Get();
-			JPH_ASSERT(group_filter_id == ioGroupFilterMap.size());
-			ioGroupFilterMap.push_back(group_filter);
-		}
-		else
-		{
-			// Existing group filter
-			group_filter = ioGroupFilterMap[group_filter_id];
-		}
+		result.SetError(gfresult.GetError());
+		return result;
 	}
 	}
-
-	// Set the group filter on the part
-	settings.mCollisionGroup.SetGroupFilter(group_filter);
+	settings.mCollisionGroup.SetGroupFilter(gfresult.Get());
 
 
 	result.Set(settings);
 	result.Set(settings);
 	return result;
 	return result;

+ 8 - 7
Jolt/Physics/Body/BodyCreationSettings.h

@@ -11,6 +11,7 @@
 #include <Jolt/Physics/Body/MotionQuality.h>
 #include <Jolt/Physics/Body/MotionQuality.h>
 #include <Jolt/Physics/Body/AllowedDOFs.h>
 #include <Jolt/Physics/Body/AllowedDOFs.h>
 #include <Jolt/ObjectStream/SerializableObject.h>
 #include <Jolt/ObjectStream/SerializableObject.h>
+#include <Jolt/Core/StreamUtils.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
 
 
@@ -22,7 +23,7 @@ enum class EOverrideMassProperties : uint8
 {
 {
 	CalculateMassAndInertia,			///< Tells the system to calculate the mass and inertia based on density
 	CalculateMassAndInertia,			///< Tells the system to calculate the mass and inertia based on density
 	CalculateInertia,					///< Tells the system to take the mass from mMassPropertiesOverride and to calculate the inertia based on density of the shapes and to scale it to the provided mass
 	CalculateInertia,					///< Tells the system to take the mass from mMassPropertiesOverride and to calculate the inertia based on density of the shapes and to scale it to the provided mass
-	MassAndInertiaProvided				///< Tells the system to take the mass and inertia from mMassPropertiesOverride 
+	MassAndInertiaProvided				///< Tells the system to take the mass and inertia from mMassPropertiesOverride
 };
 };
 
 
 /// Settings for constructing a rigid body
 /// Settings for constructing a rigid body
@@ -59,21 +60,21 @@ public:
 	/// Restore the state of this object from inStream. Doesn't restore the shape nor the group filter.
 	/// Restore the state of this object from inStream. Doesn't restore the shape nor the group filter.
 	void					RestoreBinaryState(StreamIn &inStream);
 	void					RestoreBinaryState(StreamIn &inStream);
 
 
-	using GroupFilterToIDMap = UnorderedMap<const GroupFilter *, uint32>;
-	using IDToGroupFilterMap = Array<RefConst<GroupFilter>>;
+	using GroupFilterToIDMap = StreamUtils::ObjectToIDMap<GroupFilter>;
+	using IDToGroupFilterMap = StreamUtils::IDToObjectMap<GroupFilter>;
 	using ShapeToIDMap = Shape::ShapeToIDMap;
 	using ShapeToIDMap = Shape::ShapeToIDMap;
 	using IDToShapeMap = Shape::IDToShapeMap;
 	using IDToShapeMap = Shape::IDToShapeMap;
-	using MaterialToIDMap = Shape::MaterialToIDMap;
-	using IDToMaterialMap = Shape::IDToMaterialMap;
+	using MaterialToIDMap = StreamUtils::ObjectToIDMap<PhysicsMaterial>;
+	using IDToMaterialMap = StreamUtils::IDToObjectMap<PhysicsMaterial>;
 
 
-	/// Save this body creation settings, its shape and gropu filter. Pass in an empty map in ioShapeMap / ioMaterialMap / ioGroupFilterMap or reuse the same map while saving multiple shapes to the same stream in order to avoid writing duplicates.
+	/// Save body creation settings, its shape, materials and group filter. Pass in an empty map in ioShapeMap / ioMaterialMap / ioGroupFilterMap or reuse the same map while saving multiple shapes to the same stream in order to avoid writing duplicates.
 	/// Pass nullptr to ioShapeMap and ioMaterial map to skip saving shapes
 	/// Pass nullptr to ioShapeMap and ioMaterial map to skip saving shapes
 	/// Pass nullptr to ioGroupFilterMap to skip saving group filters
 	/// Pass nullptr to ioGroupFilterMap to skip saving group filters
 	void					SaveWithChildren(StreamOut &inStream, ShapeToIDMap *ioShapeMap, MaterialToIDMap *ioMaterialMap, GroupFilterToIDMap *ioGroupFilterMap) const;
 	void					SaveWithChildren(StreamOut &inStream, ShapeToIDMap *ioShapeMap, MaterialToIDMap *ioMaterialMap, GroupFilterToIDMap *ioGroupFilterMap) const;
 
 
 	using BCSResult = Result<BodyCreationSettings>;
 	using BCSResult = Result<BodyCreationSettings>;
 
 
-	/// Restore a shape, all its children and materials. Pass in an empty map in ioShapeMap / ioMaterialMap / ioGroupFilterMap or reuse the same map while reading multiple shapes from the same stream in order to restore duplicates.
+	/// Restore body creation settings, its shape, materials and group filter. Pass in an empty map in ioShapeMap / ioMaterialMap / ioGroupFilterMap or reuse the same map while reading multiple shapes from the same stream in order to restore duplicates.
 	static BCSResult		sRestoreWithChildren(StreamIn &inStream, IDToShapeMap &ioShapeMap, IDToMaterialMap &ioMaterialMap, IDToGroupFilterMap &ioGroupFilterMap);
 	static BCSResult		sRestoreWithChildren(StreamIn &inStream, IDToShapeMap &ioShapeMap, IDToMaterialMap &ioMaterialMap, IDToGroupFilterMap &ioGroupFilterMap);
 
 
 	RVec3					mPosition = RVec3::sZero();										///< Position of the body (not of the center of mass)
 	RVec3					mPosition = RVec3::sZero();										///< Position of the body (not of the center of mass)

+ 1 - 1
Jolt/Physics/Collision/CollisionGroup.h

@@ -76,7 +76,7 @@ public:
 		else if (inOther.mGroupFilter != nullptr)
 		else if (inOther.mGroupFilter != nullptr)
 			return inOther.mGroupFilter->CanCollide(inOther, *this);
 			return inOther.mGroupFilter->CanCollide(inOther, *this);
 		else
 		else
-			return true;		
+			return true;
 	}
 	}
 
 
 	/// Saves the state of this object in binary form to inStream. Does not save group filter.
 	/// Saves the state of this object in binary form to inStream. Does not save group filter.

+ 2 - 38
Jolt/Physics/Collision/GroupFilter.cpp

@@ -5,9 +5,7 @@
 #include <Jolt/Jolt.h>
 #include <Jolt/Jolt.h>
 
 
 #include <Jolt/Physics/Collision/GroupFilter.h>
 #include <Jolt/Physics/Collision/GroupFilter.h>
-#include <Jolt/Core/StreamIn.h>
-#include <Jolt/Core/StreamOut.h>
-#include <Jolt/Core/Factory.h>
+#include <Jolt/Core/StreamUtils.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
 
 
@@ -28,41 +26,7 @@ void GroupFilter::RestoreBinaryState(StreamIn &inStream)
 
 
 GroupFilter::GroupFilterResult GroupFilter::sRestoreFromBinaryState(StreamIn &inStream)
 GroupFilter::GroupFilterResult GroupFilter::sRestoreFromBinaryState(StreamIn &inStream)
 {
 {
-	GroupFilterResult result;
-
-	// Read the type of the group filter
-	uint32 hash;
-	inStream.Read(hash);
-	if (inStream.IsEOF() || inStream.IsFailed())
-	{
-		result.SetError("Failed to read type hash");
-		return result;
-	}
-
-	// Get the RTTI for the group filter
-	const RTTI *rtti = Factory::sInstance->Find(hash);
-	if (rtti == nullptr)
-	{
-		result.SetError("Failed to create instance of group filter");
-		return result;
-	}
-
-	// Construct and read the data of the group filter
-	Ref<GroupFilter> group_filter = reinterpret_cast<GroupFilter *>(rtti->CreateObject());
-	if (group_filter == nullptr)
-	{
-		result.SetError("Failed to create instance of group filter");
-		return result;
-	}
-	group_filter->RestoreBinaryState(inStream);
-	if (inStream.IsEOF() || inStream.IsFailed())
-	{
-		result.SetError("Failed to restore group filter");
-		return result;
-	}
-
-	result.Set(group_filter);
-	return result;
+	return StreamUtils::RestoreObject<GroupFilter>(inStream, &GroupFilter::RestoreBinaryState);
 }
 }
 
 
 JPH_NAMESPACE_END
 JPH_NAMESPACE_END

+ 2 - 33
Jolt/Physics/Collision/PhysicsMaterial.cpp

@@ -6,9 +6,7 @@
 
 
 #include <Jolt/Physics/Collision/PhysicsMaterial.h>
 #include <Jolt/Physics/Collision/PhysicsMaterial.h>
 #include <Jolt/Physics/Collision/PhysicsMaterialSimple.h>
 #include <Jolt/Physics/Collision/PhysicsMaterialSimple.h>
-#include <Jolt/Core/StreamIn.h>
-#include <Jolt/Core/StreamOut.h>
-#include <Jolt/Core/Factory.h>
+#include <Jolt/Core/StreamUtils.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
 
 
@@ -31,36 +29,7 @@ void PhysicsMaterial::RestoreBinaryState(StreamIn &inStream)
 
 
 PhysicsMaterial::PhysicsMaterialResult PhysicsMaterial::sRestoreFromBinaryState(StreamIn &inStream)
 PhysicsMaterial::PhysicsMaterialResult PhysicsMaterial::sRestoreFromBinaryState(StreamIn &inStream)
 {
 {
-	PhysicsMaterialResult result;
-
-	// Read the type of the material
-	uint32 hash;
-	inStream.Read(hash);
-	if (inStream.IsEOF() || inStream.IsFailed())
-	{
-		result.SetError("Failed to read type hash");
-		return result;
-	}
-
-	// Get the RTTI for the material
-	const RTTI *rtti = Factory::sInstance->Find(hash);
-	if (rtti == nullptr)
-	{
-		result.SetError("Failed to create instance of material");
-		return result;
-	}
-
-	// Construct and read the data of the material
-	Ref<PhysicsMaterial> material = reinterpret_cast<PhysicsMaterial *>(rtti->CreateObject());
-	material->RestoreBinaryState(inStream);
-	if (inStream.IsEOF() || inStream.IsFailed())
-	{
-		result.SetError("Failed to restore material");
-		return result;
-	}
-
-	result.Set(material);
-	return result;
+	return StreamUtils::RestoreObject<PhysicsMaterial>(inStream, &PhysicsMaterial::RestoreBinaryState);
 }
 }
 
 
 JPH_NAMESPACE_END
 JPH_NAMESPACE_END

+ 5 - 65
Jolt/Physics/Collision/Shape/Shape.cpp

@@ -130,34 +130,7 @@ void Shape::SaveWithChildren(StreamOut &inStream, ShapeToIDMap &ioShapeMap, Mate
 		// Write the materials
 		// Write the materials
 		PhysicsMaterialList materials;
 		PhysicsMaterialList materials;
 		SaveMaterialState(materials);
 		SaveMaterialState(materials);
-		inStream.Write(materials.size());
-		for (const PhysicsMaterial *mat : materials)
-		{
-			if (mat == nullptr)
-			{
-				// Write nullptr
-				inStream.Write(~uint32(0));
-			}
-			else
-			{
-				MaterialToIDMap::const_iterator material_id = ioMaterialMap.find(mat);
-				if (material_id == ioMaterialMap.end())
-				{
-					// New material, write the ID
-					uint32 new_material_id = (uint32)ioMaterialMap.size();
-					ioMaterialMap[mat] = new_material_id;
-					inStream.Write(new_material_id);
-
-					// Write the material
-					mat->SaveBinaryState(inStream);
-				}
-				else
-				{
-					// Known material, just write the ID
-					inStream.Write(material_id->second);
-				}
-			}
-		}
+		StreamUtils::SaveObjectArray(inStream, materials, &ioMaterialMap);
 	}
 	}
 	else
 	else
 	{
 	{
@@ -220,46 +193,13 @@ Shape::ShapeResult Shape::sRestoreWithChildren(StreamIn &inStream, IDToShapeMap
 	result.Get()->RestoreSubShapeState(sub_shapes.data(), (uint)sub_shapes.size());
 	result.Get()->RestoreSubShapeState(sub_shapes.data(), (uint)sub_shapes.size());
 
 
 	// Read the materials
 	// Read the materials
-	inStream.Read(len);
-	if (inStream.IsEOF() || inStream.IsFailed())
+	Result mlresult = StreamUtils::RestoreObjectArray<PhysicsMaterialList>(inStream, ioMaterialMap);
+	if (mlresult.HasError())
 	{
 	{
-		result.SetError("Failed to read stream");
+		result.SetError(mlresult.GetError());
 		return result;
 		return result;
 	}
 	}
-	PhysicsMaterialList materials;
-	materials.reserve(len);
-	for (size_t i = 0; i < len; ++i)
-	{
-		Ref<PhysicsMaterial> material;
-
-		uint32 material_id;
-		inStream.Read(material_id);
-
-		// Check nullptr
-		if (material_id != ~uint32(0))
-		{
-			if (material_id >= ioMaterialMap.size())
-			{
-				// New material, restore material
-				PhysicsMaterial::PhysicsMaterialResult material_result = PhysicsMaterial::sRestoreFromBinaryState(inStream);
-				if (material_result.HasError())
-				{
-					result.SetError(material_result.GetError());
-					return result;
-				}
-				material = material_result.Get();
-				JPH_ASSERT(material_id == ioMaterialMap.size());
-				ioMaterialMap.push_back(material);
-			}
-			else
-			{
-				// Existing material
-				material = ioMaterialMap[material_id];
-			}
-		}
-
-		materials.push_back(material);
-	}
+	const PhysicsMaterialList &materials = mlresult.Get();
 	result.Get()->RestoreMaterialState(materials.data(), (uint)materials.size());
 	result.Get()->RestoreMaterialState(materials.data(), (uint)materials.size());
 
 
 	return result;
 	return result;

+ 5 - 4
Jolt/Physics/Collision/Shape/Shape.h

@@ -15,6 +15,7 @@
 #include <Jolt/Core/NonCopyable.h>
 #include <Jolt/Core/NonCopyable.h>
 #include <Jolt/Core/UnorderedMap.h>
 #include <Jolt/Core/UnorderedMap.h>
 #include <Jolt/Core/UnorderedSet.h>
 #include <Jolt/Core/UnorderedSet.h>
+#include <Jolt/Core/StreamUtils.h>
 #include <Jolt/ObjectStream/SerializableObject.h>
 #include <Jolt/ObjectStream/SerializableObject.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
@@ -371,10 +372,10 @@ public:
 	/// Restore the shape references after calling sRestoreFromBinaryState. Note that the exact same shapes need to be provided in the same order as returned by SaveSubShapeState.
 	/// Restore the shape references after calling sRestoreFromBinaryState. Note that the exact same shapes need to be provided in the same order as returned by SaveSubShapeState.
 	virtual void					RestoreSubShapeState([[maybe_unused]] const ShapeRefC *inSubShapes, uint inNumShapes) { JPH_ASSERT(inNumShapes == 0); }
 	virtual void					RestoreSubShapeState([[maybe_unused]] const ShapeRefC *inSubShapes, uint inNumShapes) { JPH_ASSERT(inNumShapes == 0); }
 
 
-	using ShapeToIDMap = UnorderedMap<const Shape *, uint32>;
-	using MaterialToIDMap = UnorderedMap<const PhysicsMaterial *, uint32>;
-	using IDToShapeMap = Array<Ref<Shape>>;
-	using IDToMaterialMap = Array<Ref<PhysicsMaterial>>;
+	using ShapeToIDMap = StreamUtils::ObjectToIDMap<Shape>;
+	using IDToShapeMap = StreamUtils::IDToObjectMap<Shape>;
+	using MaterialToIDMap = StreamUtils::ObjectToIDMap<PhysicsMaterial>;
+	using IDToMaterialMap = StreamUtils::IDToObjectMap<PhysicsMaterial>;
 
 
 	/// Save this shape, all its children and its materials. Pass in an empty map in ioShapeMap / ioMaterialMap or reuse the same map while saving multiple shapes to the same stream in order to avoid writing duplicates.
 	/// Save this shape, all its children and its materials. Pass in an empty map in ioShapeMap / ioMaterialMap or reuse the same map while saving multiple shapes to the same stream in order to avoid writing duplicates.
 	void							SaveWithChildren(StreamOut &inStream, ShapeToIDMap &ioShapeMap, MaterialToIDMap &ioMaterialMap) const;
 	void							SaveWithChildren(StreamOut &inStream, ShapeToIDMap &ioShapeMap, MaterialToIDMap &ioMaterialMap) const;

+ 2 - 33
Jolt/Physics/Constraints/Constraint.cpp

@@ -7,9 +7,7 @@
 #include <Jolt/Physics/Constraints/Constraint.h>
 #include <Jolt/Physics/Constraints/Constraint.h>
 #include <Jolt/Physics/StateRecorder.h>
 #include <Jolt/Physics/StateRecorder.h>
 #include <Jolt/ObjectStream/TypeDeclarations.h>
 #include <Jolt/ObjectStream/TypeDeclarations.h>
-#include <Jolt/Core/StreamIn.h>
-#include <Jolt/Core/StreamOut.h>
-#include <Jolt/Core/Factory.h>
+#include <Jolt/Core/StreamUtils.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
 
 
@@ -47,36 +45,7 @@ void ConstraintSettings::RestoreBinaryState(StreamIn &inStream)
 
 
 ConstraintSettings::ConstraintResult ConstraintSettings::sRestoreFromBinaryState(StreamIn &inStream)
 ConstraintSettings::ConstraintResult ConstraintSettings::sRestoreFromBinaryState(StreamIn &inStream)
 {
 {
-	ConstraintResult result;
-
-	// Read the type of the constraint
-	uint32 hash;
-	inStream.Read(hash);
-	if (inStream.IsEOF() || inStream.IsFailed())
-	{
-		result.SetError("Failed to read type id");
-		return result;
-	}
-
-	// Get the RTTI for the shape
-	const RTTI *rtti = Factory::sInstance->Find(hash);
-	if (rtti == nullptr)
-	{
-		result.SetError("Failed to resolve type. Type not registered in factory?");
-		return result;
-	}
-
-	// Construct and read the data of the shape
-	Ref<ConstraintSettings> constraint = reinterpret_cast<ConstraintSettings *>(rtti->CreateObject());
-	constraint->RestoreBinaryState(inStream);
-	if (inStream.IsEOF() || inStream.IsFailed())
-	{
-		result.SetError("Failed to restore constraint");
-		return result;
-	}
-
-	result.Set(constraint);
-	return result;
+	return StreamUtils::RestoreObject<ConstraintSettings>(inStream, &ConstraintSettings::RestoreBinaryState);
 }
 }
 
 
 void Constraint::SaveState(StateRecorder &inStream) const
 void Constraint::SaveState(StateRecorder &inStream) const

+ 3 - 34
Jolt/Physics/Constraints/PathConstraintPath.cpp

@@ -5,9 +5,7 @@
 #include <Jolt/Jolt.h>
 #include <Jolt/Jolt.h>
 
 
 #include <Jolt/Physics/Constraints/PathConstraintPath.h>
 #include <Jolt/Physics/Constraints/PathConstraintPath.h>
-#include <Jolt/Core/StreamIn.h>
-#include <Jolt/Core/StreamOut.h>
-#include <Jolt/Core/Factory.h>
+#include <Jolt/Core/StreamUtils.h>
 #ifdef JPH_DEBUG_RENDERER
 #ifdef JPH_DEBUG_RENDERER
 	#include <Jolt/Renderer/DebugRenderer.h>
 	#include <Jolt/Renderer/DebugRenderer.h>
 #endif // JPH_DEBUG_RENDERER
 #endif // JPH_DEBUG_RENDERER
@@ -68,7 +66,7 @@ void PathConstraintPath::DrawPath(DebugRenderer *inRenderer, RMat44Arg inBaseTra
 #endif // JPH_DEBUG_RENDERER
 #endif // JPH_DEBUG_RENDERER
 
 
 void PathConstraintPath::SaveBinaryState(StreamOut &inStream) const
 void PathConstraintPath::SaveBinaryState(StreamOut &inStream) const
-{ 
+{
 	inStream.Write(GetRTTI()->GetHash());
 	inStream.Write(GetRTTI()->GetHash());
 	inStream.Write(mIsLooping);
 	inStream.Write(mIsLooping);
 }
 }
@@ -81,36 +79,7 @@ void PathConstraintPath::RestoreBinaryState(StreamIn &inStream)
 
 
 PathConstraintPath::PathResult PathConstraintPath::sRestoreFromBinaryState(StreamIn &inStream)
 PathConstraintPath::PathResult PathConstraintPath::sRestoreFromBinaryState(StreamIn &inStream)
 {
 {
-	PathResult result;
-
-	// Read the type of the constraint
-	uint32 hash;
-	inStream.Read(hash);
-	if (inStream.IsEOF() || inStream.IsFailed())
-	{
-		result.SetError("Failed to read type id");
-		return result;
-	}
-
-	// Get the RTTI for the shape
-	const RTTI *rtti = Factory::sInstance->Find(hash);
-	if (rtti == nullptr)
-	{
-		result.SetError("Failed to resolve type. Type not registered in factory?");
-		return result;
-	}
-
-	// Construct and read the data of the shape
-	Ref<PathConstraintPath> path = reinterpret_cast<PathConstraintPath *>(rtti->CreateObject());
-	path->RestoreBinaryState(inStream);
-	if (inStream.IsEOF() || inStream.IsFailed())
-	{
-		result.SetError("Failed to restore constraint");
-		return result;
-	}
-
-	result.Set(path);
-	return result;
+	return StreamUtils::RestoreObject<PathConstraintPath>(inStream, &PathConstraintPath::RestoreBinaryState);
 }
 }
 
 
 JPH_NAMESPACE_END
 JPH_NAMESPACE_END

+ 48 - 4
Jolt/Physics/PhysicsScene.cpp

@@ -15,6 +15,7 @@ JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(PhysicsScene)
 {
 {
 	JPH_ADD_ATTRIBUTE(PhysicsScene, mBodies)
 	JPH_ADD_ATTRIBUTE(PhysicsScene, mBodies)
 	JPH_ADD_ATTRIBUTE(PhysicsScene, mConstraints)
 	JPH_ADD_ATTRIBUTE(PhysicsScene, mConstraints)
+	JPH_ADD_ATTRIBUTE(PhysicsScene, mSoftBodies)
 }
 }
 
 
 JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(PhysicsScene::ConnectedConstraint)
 JPH_IMPLEMENT_SERIALIZABLE_NON_VIRTUAL(PhysicsScene::ConnectedConstraint)
@@ -34,6 +35,11 @@ void PhysicsScene::AddConstraint(const TwoBodyConstraintSettings *inConstraint,
 	mConstraints.emplace_back(inConstraint, inBody1, inBody2);
 	mConstraints.emplace_back(inConstraint, inBody1, inBody2);
 }
 }
 
 
+void PhysicsScene::AddSoftBody(const SoftBodyCreationSettings &inSoftBody)
+{
+	mSoftBodies.push_back(inSoftBody);
+}
+
 bool PhysicsScene::FixInvalidScales()
 bool PhysicsScene::FixInvalidScales()
 {
 {
 	const Vec3 unit_scale = Vec3::sReplicate(1.0f);
 	const Vec3 unit_scale = Vec3::sReplicate(1.0f);
@@ -60,9 +66,10 @@ bool PhysicsScene::CreateBodies(PhysicsSystem *inSystem) const
 {
 {
 	BodyInterface &bi = inSystem->GetBodyInterface();
 	BodyInterface &bi = inSystem->GetBodyInterface();
 
 
-	// Create bodies
 	BodyIDVector body_ids;
 	BodyIDVector body_ids;
-	body_ids.reserve(mBodies.size());
+	body_ids.reserve(mBodies.size() + mSoftBodies.size());
+
+	// Create bodies
 	for (const BodyCreationSettings &b : mBodies)
 	for (const BodyCreationSettings &b : mBodies)
 	{
 	{
 		const Body *body = bi.CreateBody(b);
 		const Body *body = bi.CreateBody(b);
@@ -71,13 +78,22 @@ bool PhysicsScene::CreateBodies(PhysicsSystem *inSystem) const
 		body_ids.push_back(body->GetID());
 		body_ids.push_back(body->GetID());
 	}
 	}
 
 
+	// Create soft bodies
+	for (const SoftBodyCreationSettings &b : mSoftBodies)
+	{
+		const Body *body = bi.CreateSoftBody(b);
+		if (body == nullptr)
+			break;
+		body_ids.push_back(body->GetID());
+	}
+
 	// Batch add bodies
 	// Batch add bodies
 	BodyIDVector temp_body_ids = body_ids; // Body ID's get shuffled by AddBodiesPrepare
 	BodyIDVector temp_body_ids = body_ids; // Body ID's get shuffled by AddBodiesPrepare
 	BodyInterface::AddState add_state = bi.AddBodiesPrepare(temp_body_ids.data(), (int)temp_body_ids.size());
 	BodyInterface::AddState add_state = bi.AddBodiesPrepare(temp_body_ids.data(), (int)temp_body_ids.size());
 	bi.AddBodiesFinalize(temp_body_ids.data(), (int)temp_body_ids.size(), add_state, EActivation::Activate);
 	bi.AddBodiesFinalize(temp_body_ids.data(), (int)temp_body_ids.size(), add_state, EActivation::Activate);
 
 
 	// If not all bodies are created, creating constraints will be unreliable
 	// If not all bodies are created, creating constraints will be unreliable
-	if (body_ids.size() != mBodies.size())
+	if (body_ids.size() != mBodies.size() + mSoftBodies.size())
 		return false;
 		return false;
 
 
 	// Create constraints
 	// Create constraints
@@ -98,6 +114,7 @@ void PhysicsScene::SaveBinaryState(StreamOut &inStream, bool inSaveShapes, bool
 	BodyCreationSettings::ShapeToIDMap shape_to_id;
 	BodyCreationSettings::ShapeToIDMap shape_to_id;
 	BodyCreationSettings::MaterialToIDMap material_to_id;
 	BodyCreationSettings::MaterialToIDMap material_to_id;
 	BodyCreationSettings::GroupFilterToIDMap group_filter_to_id;
 	BodyCreationSettings::GroupFilterToIDMap group_filter_to_id;
+	SoftBodyCreationSettings::SharedSettingsToIDMap settings_to_id;
 
 
 	// Save bodies
 	// Save bodies
 	inStream.Write((uint32)mBodies.size());
 	inStream.Write((uint32)mBodies.size());
@@ -112,6 +129,11 @@ void PhysicsScene::SaveBinaryState(StreamOut &inStream, bool inSaveShapes, bool
 		inStream.Write(cc.mBody1);
 		inStream.Write(cc.mBody1);
 		inStream.Write(cc.mBody2);
 		inStream.Write(cc.mBody2);
 	}
 	}
+
+	// Save soft bodies
+	inStream.Write((uint32)mSoftBodies.size());
+	for (const SoftBodyCreationSettings &b : mSoftBodies)
+		b.SaveWithChildren(inStream, &settings_to_id, &material_to_id, inSaveGroupFilter? &group_filter_to_id : nullptr);
 }
 }
 
 
 PhysicsScene::PhysicsSceneResult PhysicsScene::sRestoreFromBinaryState(StreamIn &inStream)
 PhysicsScene::PhysicsSceneResult PhysicsScene::sRestoreFromBinaryState(StreamIn &inStream)
@@ -124,6 +146,7 @@ PhysicsScene::PhysicsSceneResult PhysicsScene::sRestoreFromBinaryState(StreamIn
 	BodyCreationSettings::IDToShapeMap id_to_shape;
 	BodyCreationSettings::IDToShapeMap id_to_shape;
 	BodyCreationSettings::IDToMaterialMap id_to_material;
 	BodyCreationSettings::IDToMaterialMap id_to_material;
 	BodyCreationSettings::IDToGroupFilterMap id_to_group_filter;
 	BodyCreationSettings::IDToGroupFilterMap id_to_group_filter;
+	SoftBodyCreationSettings::IDToSharedSettingsMap id_to_settings;
 
 
 	// Reserve some memory to avoid frequent reallocations
 	// Reserve some memory to avoid frequent reallocations
 	id_to_shape.reserve(1024);
 	id_to_shape.reserve(1024);
@@ -163,6 +186,22 @@ PhysicsScene::PhysicsSceneResult PhysicsScene::sRestoreFromBinaryState(StreamIn
 		inStream.Read(cc.mBody2);
 		inStream.Read(cc.mBody2);
 	}
 	}
 
 
+	// Read soft bodies
+	len = 0;
+	inStream.Read(len);
+	scene->mSoftBodies.resize(len);
+	for (SoftBodyCreationSettings &b : scene->mSoftBodies)
+	{
+		// Read creation settings
+		SoftBodyCreationSettings::SBCSResult sbcs_result = SoftBodyCreationSettings::sRestoreWithChildren(inStream, id_to_settings, id_to_material, id_to_group_filter);
+		if (sbcs_result.HasError())
+		{
+			result.SetError(sbcs_result.GetError());
+			return result;
+		}
+		b = sbcs_result.Get();
+	}
+
 	result.Set(scene);
 	result.Set(scene);
 	return result;
 	return result;
 }
 }
@@ -190,8 +229,13 @@ void PhysicsScene::FromPhysicsSystem(const PhysicsSystem *inSystem)
 			// Store location of body
 			// Store location of body
 			body_id_to_idx[id] = (uint32)mBodies.size();
 			body_id_to_idx[id] = (uint32)mBodies.size();
 
 
+			const Body &body = lock.GetBody();
+
 			// Convert to body creation settings
 			// Convert to body creation settings
-			AddBody(lock.GetBody().GetBodyCreationSettings());
+			if (body.IsRigidBody())
+				AddBody(body.GetBodyCreationSettings());
+			else
+				AddSoftBody(body.GetSoftBodyCreationSettings());
 		}
 		}
 	}
 	}
 
 

+ 14 - 0
Jolt/Physics/PhysicsScene.h

@@ -6,6 +6,7 @@
 
 
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Physics/Body/BodyCreationSettings.h>
 #include <Jolt/Physics/Body/BodyCreationSettings.h>
+#include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
 #include <Jolt/Physics/Constraints/TwoBodyConstraint.h>
 #include <Jolt/Physics/Constraints/TwoBodyConstraint.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
@@ -30,6 +31,9 @@ public:
 	/// @param inBody2 Index in the bodies list of the second body to attach constraint to
 	/// @param inBody2 Index in the bodies list of the second body to attach constraint to
 	void									AddConstraint(const TwoBodyConstraintSettings *inConstraint, uint32 inBody1, uint32 inBody2);
 	void									AddConstraint(const TwoBodyConstraintSettings *inConstraint, uint32 inBody1, uint32 inBody2);
 
 
+	/// Add a soft body to the scene
+	void									AddSoftBody(const SoftBodyCreationSettings &inSoftBody);
+
 	/// Get number of bodies in this scene
 	/// Get number of bodies in this scene
 	size_t									GetNumBodies() const							{ return mBodies.size(); }
 	size_t									GetNumBodies() const							{ return mBodies.size(); }
 
 
@@ -58,6 +62,13 @@ public:
 	const Array<ConnectedConstraint> &		GetConstraints() const							{ return mConstraints; }
 	const Array<ConnectedConstraint> &		GetConstraints() const							{ return mConstraints; }
 	Array<ConnectedConstraint> &			GetConstraints()								{ return mConstraints; }
 	Array<ConnectedConstraint> &			GetConstraints()								{ return mConstraints; }
 
 
+	/// Get number of bodies in this scene
+	size_t									GetNumSoftBodies() const						{ return mSoftBodies.size(); }
+
+	/// Access to the soft body settings for this scene
+	const Array<SoftBodyCreationSettings> &	GetSoftBodies() const							{ return mSoftBodies; }
+	Array<SoftBodyCreationSettings> &		GetSoftBodies()									{ return mSoftBodies; }
+
 	/// Instantiate all bodies, returns false if not all bodies could be created
 	/// Instantiate all bodies, returns false if not all bodies could be created
 	bool									CreateBodies(PhysicsSystem *inSystem) const;
 	bool									CreateBodies(PhysicsSystem *inSystem) const;
 
 
@@ -85,6 +96,9 @@ private:
 
 
 	/// Constraints that are part of this scene
 	/// Constraints that are part of this scene
 	Array<ConnectedConstraint>				mConstraints;
 	Array<ConnectedConstraint>				mConstraints;
+
+	/// Soft bodies that are part of this scene
+	Array<SoftBodyCreationSettings>			mSoftBodies;
 };
 };
 
 
 JPH_NAMESPACE_END
 JPH_NAMESPACE_END

+ 50 - 0
Jolt/Physics/SoftBody/SoftBodyCreationSettings.cpp

@@ -60,4 +60,54 @@ void SoftBodyCreationSettings::RestoreBinaryState(StreamIn &inStream)
 	inStream.Read(mMakeRotationIdentity);
 	inStream.Read(mMakeRotationIdentity);
 }
 }
 
 
+void SoftBodyCreationSettings::SaveWithChildren(StreamOut &inStream, SharedSettingsToIDMap *ioSharedSettingsMap, MaterialToIDMap *ioMaterialMap, GroupFilterToIDMap *ioGroupFilterMap) const
+{
+	// Save creation settings
+	SaveBinaryState(inStream);
+
+	// Save shared settings
+	if (ioSharedSettingsMap != nullptr && ioMaterialMap != nullptr)
+		mSettings->SaveWithMaterials(inStream, *ioSharedSettingsMap, *ioMaterialMap);
+	else
+		inStream.Write(~uint32(0));
+
+	// Save group filter
+	StreamUtils::SaveObjectReference(inStream, mCollisionGroup.GetGroupFilter(), ioGroupFilterMap);
+}
+
+SoftBodyCreationSettings::SBCSResult SoftBodyCreationSettings::sRestoreWithChildren(StreamIn &inStream, IDToSharedSettingsMap &ioSharedSettingsMap, IDToMaterialMap &ioMaterialMap, IDToGroupFilterMap &ioGroupFilterMap)
+{
+	SBCSResult result;
+
+	// Read creation settings
+	SoftBodyCreationSettings settings;
+	settings.RestoreBinaryState(inStream);
+	if (inStream.IsEOF() || inStream.IsFailed())
+	{
+		result.SetError("Error reading body creation settings");
+		return result;
+	}
+
+	// Read shared settings
+	SoftBodySharedSettings::SettingsResult settings_result = SoftBodySharedSettings::sRestoreWithMaterials(inStream, ioSharedSettingsMap, ioMaterialMap);
+	if (settings_result.HasError())
+	{
+		result.SetError(settings_result.GetError());
+		return result;
+	}
+	settings.mSettings = settings_result.Get();
+
+	// Read group filter
+	Result gfresult = StreamUtils::RestoreObjectReference(inStream, ioGroupFilterMap);
+	if (gfresult.HasError())
+	{
+		result.SetError(gfresult.GetError());
+		return result;
+	}
+	settings.mCollisionGroup.SetGroupFilter(gfresult.Get());
+
+	result.Set(settings);
+	return result;
+}
+
 JPH_NAMESPACE_END
 JPH_NAMESPACE_END

+ 18 - 0
Jolt/Physics/SoftBody/SoftBodyCreationSettings.h

@@ -8,6 +8,7 @@
 #include <Jolt/Physics/Collision/ObjectLayer.h>
 #include <Jolt/Physics/Collision/ObjectLayer.h>
 #include <Jolt/Physics/Collision/CollisionGroup.h>
 #include <Jolt/Physics/Collision/CollisionGroup.h>
 #include <Jolt/ObjectStream/SerializableObject.h>
 #include <Jolt/ObjectStream/SerializableObject.h>
+#include <Jolt/Core/StreamUtils.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
 
 
@@ -28,6 +29,23 @@ public:
 	/// Restore the state of this object from inStream. Doesn't restore the shared settings nor the group filter.
 	/// Restore the state of this object from inStream. Doesn't restore the shared settings nor the group filter.
 	void				RestoreBinaryState(StreamIn &inStream);
 	void				RestoreBinaryState(StreamIn &inStream);
 
 
+	using GroupFilterToIDMap = StreamUtils::ObjectToIDMap<GroupFilter>;
+	using IDToGroupFilterMap = StreamUtils::IDToObjectMap<GroupFilter>;
+	using SharedSettingsToIDMap = SoftBodySharedSettings::SharedSettingsToIDMap;
+	using IDToSharedSettingsMap = SoftBodySharedSettings::IDToSharedSettingsMap;
+	using MaterialToIDMap = StreamUtils::ObjectToIDMap<PhysicsMaterial>;
+	using IDToMaterialMap = StreamUtils::IDToObjectMap<PhysicsMaterial>;
+
+	/// Save this body creation settings, its shared settings and group filter. Pass in an empty map in ioSharedSettingsMap / ioMaterialMap / ioGroupFilterMap or reuse the same map while saving multiple shapes to the same stream in order to avoid writing duplicates.
+	/// Pass nullptr to ioSharedSettingsMap and ioMaterial map to skip saving shared settings and materials
+	/// Pass nullptr to ioGroupFilterMap to skip saving group filters
+	void				SaveWithChildren(StreamOut &inStream, SharedSettingsToIDMap *ioSharedSettingsMap, MaterialToIDMap *ioMaterialMap, GroupFilterToIDMap *ioGroupFilterMap) const;
+
+	using SBCSResult = Result<SoftBodyCreationSettings>;
+
+	/// Restore a shape, all its children and materials. Pass in an empty map in ioSharedSettingsMap / ioMaterialMap / ioGroupFilterMap or reuse the same map while reading multiple shapes from the same stream in order to restore duplicates.
+	static SBCSResult	sRestoreWithChildren(StreamIn &inStream, IDToSharedSettingsMap &ioSharedSettingsMap, IDToMaterialMap &ioMaterialMap, IDToGroupFilterMap &ioGroupFilterMap);
+
 	RefConst<SoftBodySharedSettings> mSettings;				///< Defines the configuration of this soft body
 	RefConst<SoftBodySharedSettings> mSettings;				///< Defines the configuration of this soft body
 
 
 	RVec3				mPosition { RVec3::sZero() };		///< Initial position of the soft body
 	RVec3				mPosition { RVec3::sZero() };		///< Initial position of the soft body

+ 72 - 0
Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp

@@ -89,4 +89,76 @@ void SoftBodySharedSettings::RestoreBinaryState(StreamIn &inStream)
 	inStream.Read(mVolumeConstraints);
 	inStream.Read(mVolumeConstraints);
 }
 }
 
 
+void SoftBodySharedSettings::SaveWithMaterials(StreamOut &inStream, SharedSettingsToIDMap &ioSettingsMap, MaterialToIDMap &ioMaterialMap) const
+{
+	SharedSettingsToIDMap::const_iterator settings_iter = ioSettingsMap.find(this);
+	if (settings_iter == ioSettingsMap.end())
+	{
+		// Write settings ID
+		uint32 settings_id = (uint32)ioSettingsMap.size();
+		ioSettingsMap[this] = settings_id;
+		inStream.Write(settings_id);
+
+		// Write the settings
+		SaveBinaryState(inStream);
+
+		// Write materials
+		StreamUtils::SaveObjectArray(inStream, mMaterials, &ioMaterialMap);
+	}
+	else
+	{
+		// Known settings, just write the ID
+		inStream.Write(settings_iter->second);
+	}
+}
+
+SoftBodySharedSettings::SettingsResult SoftBodySharedSettings::sRestoreWithMaterials(StreamIn &inStream, IDToSharedSettingsMap &ioSettingsMap, IDToMaterialMap &ioMaterialMap)
+{
+	SettingsResult result;
+
+	// Read settings id
+	uint32 settings_id;
+	inStream.Read(settings_id);
+	if (inStream.IsEOF() || inStream.IsFailed())
+	{
+		result.SetError("Failed to read settings id");
+		return result;
+	}
+
+	// Check nullptr settings
+	if (settings_id == ~uint32(0))
+	{
+		result.Set(nullptr);
+		return result;
+	}
+
+	// Check if we already read this settings
+	if (settings_id < ioSettingsMap.size())
+	{
+		result.Set(ioSettingsMap[settings_id]);
+		return result;
+	}
+
+	// Create new object
+	Ref<SoftBodySharedSettings> settings = new SoftBodySharedSettings;
+
+	// Read state
+	settings->RestoreBinaryState(inStream);
+
+	// Read materials
+	Result mlresult = StreamUtils::RestoreObjectArray<PhysicsMaterialList>(inStream, ioMaterialMap);
+	if (mlresult.HasError())
+	{
+		result.SetError(mlresult.GetError());
+		return result;
+	}
+	settings->mMaterials = mlresult.Get();
+
+	// Add the settings to the map
+	ioSettingsMap.push_back(settings);
+
+	result.Set(settings);
+	return result;
+}
+
 JPH_NAMESPACE_END
 JPH_NAMESPACE_END

+ 14 - 0
Jolt/Physics/SoftBody/SoftBodySharedSettings.h

@@ -6,6 +6,7 @@
 
 
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Core/Reference.h>
 #include <Jolt/Physics/Collision/PhysicsMaterial.h>
 #include <Jolt/Physics/Collision/PhysicsMaterial.h>
+#include <Jolt/Core/StreamUtils.h>
 
 
 JPH_NAMESPACE_BEGIN
 JPH_NAMESPACE_BEGIN
 
 
@@ -28,6 +29,19 @@ public:
 	/// Restore the state of this object from inStream. Doesn't restore the material list.
 	/// Restore the state of this object from inStream. Doesn't restore the material list.
 	void				RestoreBinaryState(StreamIn &inStream);
 	void				RestoreBinaryState(StreamIn &inStream);
 
 
+	using SharedSettingsToIDMap = StreamUtils::ObjectToIDMap<SoftBodySharedSettings>;
+	using IDToSharedSettingsMap = StreamUtils::IDToObjectMap<SoftBodySharedSettings>;
+	using MaterialToIDMap = StreamUtils::ObjectToIDMap<PhysicsMaterial>;
+	using IDToMaterialMap = StreamUtils::IDToObjectMap<PhysicsMaterial>;
+
+	/// Save this shared settings and its materials. Pass in an empty map ioSettingsMap / ioMaterialMap or reuse the same map while saving multiple settings objects to the same stream in order to avoid writing duplicates.
+	void				SaveWithMaterials(StreamOut &inStream, SharedSettingsToIDMap &ioSettingsMap, MaterialToIDMap &ioMaterialMap) const;
+
+	using SettingsResult = Result<Ref<SoftBodySharedSettings>>;
+
+	/// Restore a shape and materials. Pass in an empty map in ioSettingsMap / ioMaterialMap or reuse the same map while reading multiple settings objects from the same stream in order to restore duplicates.
+	static SettingsResult sRestoreWithMaterials(StreamIn &inStream, IDToSharedSettingsMap &ioSettingsMap, IDToMaterialMap &ioMaterialMap);
+
 	/// A vertex is a particle, the data in this structure is only used during creation of the soft body and not during simulation
 	/// A vertex is a particle, the data in this structure is only used during creation of the soft body and not during simulation
 	struct JPH_EXPORT Vertex
 	struct JPH_EXPORT Vertex
 	{
 	{

+ 23 - 3
Samples/Tests/General/LoadSaveSceneTest.cpp

@@ -26,10 +26,11 @@
 #include <Jolt/Physics/Body/BodyCreationSettings.h>
 #include <Jolt/Physics/Body/BodyCreationSettings.h>
 #include <Layers.h>
 #include <Layers.h>
 #include <Utils/Log.h>
 #include <Utils/Log.h>
+#include <Utils/SoftBodyCreator.h>
 
 
-JPH_IMPLEMENT_RTTI_VIRTUAL(LoadSaveSceneTest) 
-{ 
-	JPH_ADD_BASE_CLASS(LoadSaveSceneTest, Test) 
+JPH_IMPLEMENT_RTTI_VIRTUAL(LoadSaveSceneTest)
+{
+	JPH_ADD_BASE_CLASS(LoadSaveSceneTest, Test)
 }
 }
 
 
 static const float cMaxHeight = 4.0f;
 static const float cMaxHeight = 4.0f;
@@ -177,6 +178,25 @@ Ref<PhysicsScene> LoadSaveSceneTest::sCreateScene()
 	dist_constraint->mSpace = EConstraintSpace::LocalToBodyCOM;
 	dist_constraint->mSpace = EConstraintSpace::LocalToBodyCOM;
 	scene->AddConstraint(dist_constraint, 3, 4);
 	scene->AddConstraint(dist_constraint, 3, 4);
 
 
+	// Add soft body cube
+	Ref<SoftBodySharedSettings> sb_cube_settings = SoftBodyCreator::CreateCube(5, 0.2f);
+	sb_cube_settings->mMaterials = { new PhysicsMaterialSimple("Soft Body Cube Material", Color::sGetDistinctColor(13)) };
+	SoftBodyCreationSettings sb_cube(sb_cube_settings, RVec3(0, cMaxHeight + 10.0f, 0));
+	sb_cube.mObjectLayer = Layers::MOVING;
+	scene->AddSoftBody(sb_cube);
+
+	// Add the same shape again to test sharing
+	sb_cube.mPosition = RVec3(0, cMaxHeight + 11.0f, 0);
+	scene->AddSoftBody(sb_cube);
+
+	// Add soft body sphere
+	Ref<SoftBodySharedSettings> sb_sphere_settings = SoftBodyCreator::CreateSphere(0.5f);
+	sb_sphere_settings->mMaterials = { new PhysicsMaterialSimple("Soft Body Sphere Material", Color::sGetDistinctColor(14)) };
+	SoftBodyCreationSettings sb_sphere(sb_sphere_settings, RVec3(0, cMaxHeight + 12.0f, 0));
+	sb_sphere.mObjectLayer = Layers::MOVING;
+	sb_sphere.mPressure = 2000.0f;
+	scene->AddSoftBody(sb_sphere);
+
 	return scene;
 	return scene;
 }
 }