Browse Source

BodyInterface::AddForce applied a force per vertex rather than to the whole body (#1412)

This resulted in a soft body accelerating much compared to a rigid body of the same mass.
Added SoftBodySharedSettings::sCreateCube for testing purposes.
Jorrit Rouwe 7 months ago
parent
commit
7850b05a97

+ 1 - 0
Docs/ReleaseNotes.md

@@ -22,6 +22,7 @@ For breaking API changes see [this document](https://github.com/jrouwe/JoltPhysi
 * `std::push_heap`/`pop_heap` behave differently on macOS vs Windows/Linux when elements compare equal, this made the cross platform deterministic build not deterministic in some cases.
 * `std::push_heap`/`pop_heap` behave differently on macOS vs Windows/Linux when elements compare equal, this made the cross platform deterministic build not deterministic in some cases.
 * Added overloads for placement new in the `JPH_OVERRIDE_NEW_DELETE` macro, this means it is no longer needed to do `:: new (address) JPH::class_name(constructor_arguments)` but you can do `new (address) JPH::class_name(constructor_arguments)`.
 * Added overloads for placement new in the `JPH_OVERRIDE_NEW_DELETE` macro, this means it is no longer needed to do `:: new (address) JPH::class_name(constructor_arguments)` but you can do `new (address) JPH::class_name(constructor_arguments)`.
 * Fixed a GCC warning `-Wshadow=global`.
 * Fixed a GCC warning `-Wshadow=global`.
+* BodyInterface::AddForce applied a force per vertex rather than to the whole body, this resulted in a soft body accelerating much compared to a rigid body of the same mass.
 
 
 ## v5.2.0
 ## v5.2.0
 
 

+ 3 - 3
Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp

@@ -315,7 +315,7 @@ void SoftBodyMotionProperties::IntegratePositions(const SoftBodyUpdateContext &i
 
 
 	// Integrate
 	// Integrate
 	Vec3 sub_step_gravity = inContext.mGravity * dt;
 	Vec3 sub_step_gravity = inContext.mGravity * dt;
-	Vec3 sub_step_impulse = GetAccumulatedForce() * dt;
+	Vec3 sub_step_impulse = GetAccumulatedForce() * dt / max(float(mVertices.size()), 1.0f);
 	for (Vertex &v : mVertices)
 	for (Vertex &v : mVertices)
 		if (v.mInvMass > 0.0f)
 		if (v.mInvMass > 0.0f)
 		{
 		{
@@ -765,8 +765,8 @@ void SoftBodyMotionProperties::UpdateSoftBodyState(SoftBodyUpdateContext &ioCont
 
 
 	// Calculate linear/angular velocity of the body by averaging all vertices and bringing the value to world space
 	// Calculate linear/angular velocity of the body by averaging all vertices and bringing the value to world space
 	float num_vertices_divider = float(max(int(mVertices.size()), 1));
 	float num_vertices_divider = float(max(int(mVertices.size()), 1));
-	SetLinearVelocity(ioContext.mCenterOfMassTransform.Multiply3x3(linear_velocity / num_vertices_divider));
-	SetAngularVelocity(ioContext.mCenterOfMassTransform.Multiply3x3(angular_velocity / num_vertices_divider));
+	SetLinearVelocityClamped(ioContext.mCenterOfMassTransform.Multiply3x3(linear_velocity / num_vertices_divider));
+	SetAngularVelocityClamped(ioContext.mCenterOfMassTransform.Multiply3x3(angular_velocity / num_vertices_divider));
 
 
 	if (mUpdatePosition)
 	if (mUpdatePosition)
 	{
 	{

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

@@ -1029,4 +1029,147 @@ SoftBodySharedSettings::SettingsResult SoftBodySharedSettings::sRestoreWithMater
 	return result;
 	return result;
 }
 }
 
 
+Ref<SoftBodySharedSettings> SoftBodySharedSettings::sCreateCube(uint inGridSize, float inGridSpacing)
+{
+	const Vec3 cOffset = Vec3::sReplicate(-0.5f * inGridSpacing * (inGridSize - 1));
+
+	// Create settings
+	SoftBodySharedSettings *settings = new SoftBodySharedSettings;
+	for (uint z = 0; z < inGridSize; ++z)
+		for (uint y = 0; y < inGridSize; ++y)
+			for (uint x = 0; x < inGridSize; ++x)
+			{
+				SoftBodySharedSettings::Vertex v;
+				(cOffset + Vec3::sReplicate(inGridSpacing) * Vec3(float(x), float(y), float(z))).StoreFloat3(&v.mPosition);
+				settings->mVertices.push_back(v);
+			}
+
+	// Function to get the vertex index of a point on the cube
+	auto vertex_index = [inGridSize](uint inX, uint inY, uint inZ)
+	{
+		return inX + inY * inGridSize + inZ * inGridSize * inGridSize;
+	};
+
+	// Create edges
+	for (uint z = 0; z < inGridSize; ++z)
+		for (uint y = 0; y < inGridSize; ++y)
+			for (uint x = 0; x < inGridSize; ++x)
+			{
+				SoftBodySharedSettings::Edge e;
+				e.mVertex[0] = vertex_index(x, y, z);
+				if (x < inGridSize - 1)
+				{
+					e.mVertex[1] = vertex_index(x + 1, y, z);
+					settings->mEdgeConstraints.push_back(e);
+				}
+				if (y < inGridSize - 1)
+				{
+					e.mVertex[1] = vertex_index(x, y + 1, z);
+					settings->mEdgeConstraints.push_back(e);
+				}
+				if (z < inGridSize - 1)
+				{
+					e.mVertex[1] = vertex_index(x, y, z + 1);
+					settings->mEdgeConstraints.push_back(e);
+				}
+			}
+	settings->CalculateEdgeLengths();
+
+	// Tetrahedrons to fill a cube
+	const int tetra_indices[6][4][3] = {
+		{ {0, 0, 0}, {0, 1, 1}, {0, 0, 1}, {1, 1, 1} },
+		{ {0, 0, 0}, {0, 1, 0}, {0, 1, 1}, {1, 1, 1} },
+		{ {0, 0, 0}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1} },
+		{ {0, 0, 0}, {1, 0, 1}, {1, 0, 0}, {1, 1, 1} },
+		{ {0, 0, 0}, {1, 1, 0}, {0, 1, 0}, {1, 1, 1} },
+		{ {0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {1, 1, 1} }
+	};
+
+	// Create volume constraints
+	for (uint z = 0; z < inGridSize - 1; ++z)
+		for (uint y = 0; y < inGridSize - 1; ++y)
+			for (uint x = 0; x < inGridSize - 1; ++x)
+				for (uint t = 0; t < 6; ++t)
+				{
+					SoftBodySharedSettings::Volume v;
+					for (uint i = 0; i < 4; ++i)
+						v.mVertex[i] = vertex_index(x + tetra_indices[t][i][0], y + tetra_indices[t][i][1], z + tetra_indices[t][i][2]);
+					settings->mVolumeConstraints.push_back(v);
+				}
+
+	settings->CalculateVolumeConstraintVolumes();
+
+	// Create faces
+	for (uint y = 0; y < inGridSize - 1; ++y)
+		for (uint x = 0; x < inGridSize - 1; ++x)
+		{
+			SoftBodySharedSettings::Face f;
+
+			// Face 1
+			f.mVertex[0] = vertex_index(x, y, 0);
+			f.mVertex[1] = vertex_index(x, y + 1, 0);
+			f.mVertex[2] = vertex_index(x + 1, y + 1, 0);
+			settings->AddFace(f);
+
+			f.mVertex[1] = vertex_index(x + 1, y + 1, 0);
+			f.mVertex[2] = vertex_index(x + 1, y, 0);
+			settings->AddFace(f);
+
+			// Face 2
+			f.mVertex[0] = vertex_index(x, y, inGridSize - 1);
+			f.mVertex[1] = vertex_index(x + 1, y + 1, inGridSize - 1);
+			f.mVertex[2] = vertex_index(x, y + 1, inGridSize - 1);
+			settings->AddFace(f);
+
+			f.mVertex[1] = vertex_index(x + 1, y, inGridSize - 1);
+			f.mVertex[2] = vertex_index(x + 1, y + 1, inGridSize - 1);
+			settings->AddFace(f);
+
+			// Face 3
+			f.mVertex[0] = vertex_index(x, 0, y);
+			f.mVertex[1] = vertex_index(x + 1, 0, y + 1);
+			f.mVertex[2] = vertex_index(x, 0, y + 1);
+			settings->AddFace(f);
+
+			f.mVertex[1] = vertex_index(x + 1, 0, y);
+			f.mVertex[2] = vertex_index(x + 1, 0, y + 1);
+			settings->AddFace(f);
+
+			// Face 4
+			f.mVertex[0] = vertex_index(x, inGridSize - 1, y);
+			f.mVertex[1] = vertex_index(x, inGridSize - 1, y + 1);
+			f.mVertex[2] = vertex_index(x + 1, inGridSize - 1, y + 1);
+			settings->AddFace(f);
+
+			f.mVertex[1] = vertex_index(x + 1, inGridSize - 1, y + 1);
+			f.mVertex[2] = vertex_index(x + 1, inGridSize - 1, y);
+			settings->AddFace(f);
+
+			// Face 5
+			f.mVertex[0] = vertex_index(0, x, y);
+			f.mVertex[1] = vertex_index(0, x, y + 1);
+			f.mVertex[2] = vertex_index(0, x + 1, y + 1);
+			settings->AddFace(f);
+
+			f.mVertex[1] = vertex_index(0, x + 1, y + 1);
+			f.mVertex[2] = vertex_index(0, x + 1, y);
+			settings->AddFace(f);
+
+			// Face 6
+			f.mVertex[0] = vertex_index(inGridSize - 1, x, y);
+			f.mVertex[1] = vertex_index(inGridSize - 1, x + 1, y + 1);
+			f.mVertex[2] = vertex_index(inGridSize - 1, x, y + 1);
+			settings->AddFace(f);
+
+			f.mVertex[1] = vertex_index(inGridSize - 1, x + 1, y);
+			f.mVertex[2] = vertex_index(inGridSize - 1, x + 1, y + 1);
+			settings->AddFace(f);
+		}
+
+	// Optimize the settings
+	settings->Optimize();
+
+	return settings;
+}
+
 JPH_NAMESPACE_END
 JPH_NAMESPACE_END

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

@@ -111,6 +111,12 @@ public:
 	/// 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.
 	/// 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);
 	static SettingsResult sRestoreWithMaterials(StreamIn &inStream, IDToSharedSettingsMap &ioSettingsMap, IDToMaterialMap &ioMaterialMap);
 
 
+	/// Create a cube. This can be used to create a simple soft body for testing purposes.
+	/// It will contain edge constraints, volume constraints and faces.
+	/// @param inGridSize Number of points along each axis
+	/// @param inGridSpacing Distance between points
+	static Ref<SoftBodySharedSettings> sCreateCube(uint inGridSize, float inGridSpacing);
+
 	/// 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
 	{
 	{

+ 1 - 1
Samples/SamplesApp.cpp

@@ -1020,7 +1020,7 @@ void SamplesApp::ShootObject()
 	}
 	}
 	else
 	else
 	{
 	{
-		Ref<SoftBodySharedSettings> shared_settings = SoftBodyCreator::CreateCube(5, 0.5f * GetWorldScale());
+		Ref<SoftBodySharedSettings> shared_settings = SoftBodySharedSettings::sCreateCube(5, 0.5f * GetWorldScale());
 		for (SoftBodySharedSettings::Vertex &v : shared_settings->mVertices)
 		for (SoftBodySharedSettings::Vertex &v : shared_settings->mVertices)
 		{
 		{
 			v.mInvMass = 0.025f;
 			v.mInvMass = 0.025f;

+ 1 - 1
Samples/Tests/General/LoadSaveSceneTest.cpp

@@ -189,7 +189,7 @@ Ref<PhysicsScene> LoadSaveSceneTest::sCreateScene()
 	scene->AddConstraint(dist_constraint, 3, 4);
 	scene->AddConstraint(dist_constraint, 3, 4);
 
 
 	// Add soft body cube
 	// Add soft body cube
-	Ref<SoftBodySharedSettings> sb_cube_settings = SoftBodyCreator::CreateCube(5, 0.2f);
+	Ref<SoftBodySharedSettings> sb_cube_settings = SoftBodySharedSettings::sCreateCube(5, 0.2f);
 	sb_cube_settings->mMaterials = { new PhysicsMaterialSimple("Soft Body Cube Material", next_color()) };
 	sb_cube_settings->mMaterials = { new PhysicsMaterialSimple("Soft Body Cube Material", next_color()) };
 	SoftBodyCreationSettings sb_cube(sb_cube_settings, next_pos(), Quat::sIdentity(), Layers::MOVING);
 	SoftBodyCreationSettings sb_cube(sb_cube_settings, next_pos(), Quat::sIdentity(), Layers::MOVING);
 	scene->AddSoftBody(sb_cube);
 	scene->AddSoftBody(sb_cube);

+ 2 - 2
Samples/Tests/SoftBody/SoftBodyForceTest.cpp

@@ -37,7 +37,7 @@ void SoftBodyForceTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 	mTime += inParams.mDeltaTime;
 	mTime += inParams.mDeltaTime;
 
 
 	// Apply a fluctuating force
 	// Apply a fluctuating force
-	constexpr float cMaxForce = 15.0f;
+	constexpr float cMaxForce = 10000.0f;
 	constexpr float cMaxAngle = DegreesToRadians(90.0f);
 	constexpr float cMaxAngle = DegreesToRadians(90.0f);
 	Vec3 force(0, 0, 0.5f * cMaxForce * (1.0f + PerlinNoise3(0, 0, mTime / 2.0f, 256, 256, 256)));
 	Vec3 force(0, 0, 0.5f * cMaxForce * (1.0f + PerlinNoise3(0, 0, mTime / 2.0f, 256, 256, 256)));
 	force = Mat44::sRotationY(cMaxAngle * PerlinNoise3(mTime / 10.0f, 0, 0, 256, 256, 256)) * force;
 	force = Mat44::sRotationY(cMaxAngle * PerlinNoise3(mTime / 10.0f, 0, 0, 256, 256, 256)) * force;
@@ -45,7 +45,7 @@ void SoftBodyForceTest::PrePhysicsUpdate(const PreUpdateParams &inParams)
 
 
 	// Draw the force
 	// Draw the force
 	RVec3 offset(0, 10, 0);
 	RVec3 offset(0, 10, 0);
-	DebugRenderer::sInstance->DrawArrow(offset, offset + force, Color::sGreen, 0.1f);
+	DebugRenderer::sInstance->DrawArrow(offset, offset + 10.0f * force.Normalized(), Color::sGreen, 0.1f);
 }
 }
 
 
 void SoftBodyForceTest::SaveState(StateRecorder &inStream) const
 void SoftBodyForceTest::SaveState(StateRecorder &inStream) const

+ 1 - 1
Samples/Tests/SoftBody/SoftBodyFrictionTest.cpp

@@ -34,7 +34,7 @@ void SoftBodyFrictionTest::Initialize()
 		mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
 		mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
 	}
 	}
 
 
-	Ref<SoftBodySharedSettings> cube_settings = SoftBodyCreator::CreateCube();
+	Ref<SoftBodySharedSettings> cube_settings = SoftBodySharedSettings::sCreateCube(5, 0.5f);
 	for (SoftBodySharedSettings::Vertex &v : cube_settings->mVertices)
 	for (SoftBodySharedSettings::Vertex &v : cube_settings->mVertices)
 		v.mVelocity = Float3(0, 0, 10);
 		v.mVelocity = Float3(0, 0, 10);
 	SoftBodyCreationSettings cube(cube_settings, RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
 	SoftBodyCreationSettings cube(cube_settings, RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);

+ 1 - 1
Samples/Tests/SoftBody/SoftBodyGravityFactorTest.cpp

@@ -30,7 +30,7 @@ void SoftBodyGravityFactorTest::Initialize()
 		mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
 		mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
 	}
 	}
 
 
-	SoftBodyCreationSettings cube(SoftBodyCreator::CreateCube(), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
+	SoftBodyCreationSettings cube(SoftBodySharedSettings::sCreateCube(5, 0.5f), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
 
 
 	for (int i = 0; i <= 10; ++i)
 	for (int i = 0; i <= 10; ++i)
 	{
 	{

+ 1 - 1
Samples/Tests/SoftBody/SoftBodyRestitutionTest.cpp

@@ -31,7 +31,7 @@ void SoftBodyRestitutionTest::Initialize()
 		mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
 		mBodyInterface->CreateAndAddSoftBody(sphere, EActivation::Activate);
 	}
 	}
 
 
-	SoftBodyCreationSettings cube(SoftBodyCreator::CreateCube(), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
+	SoftBodyCreationSettings cube(SoftBodySharedSettings::sCreateCube(5, 0.5f), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
 
 
 	for (int i = 0; i <= 10; ++i)
 	for (int i = 0; i <= 10; ++i)
 	{
 	{

+ 1 - 1
Samples/Tests/SoftBody/SoftBodyShapesTest.cpp

@@ -39,7 +39,7 @@ void SoftBodyShapesTest::Initialize()
 	mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
 	mBodyInterface->CreateAndAddSoftBody(cloth, EActivation::Activate);
 
 
 	// Create cube
 	// Create cube
-	SoftBodyCreationSettings cube(SoftBodyCreator::CreateCube(), RVec3(20.0f, 10.0f, 0.0f), cCubeOrientation, Layers::MOVING);
+	SoftBodyCreationSettings cube(SoftBodySharedSettings::sCreateCube(5, 0.5f), RVec3(20.0f, 10.0f, 0.0f), cCubeOrientation, Layers::MOVING);
 	cube.mRestitution = 0.0f;
 	cube.mRestitution = 0.0f;
 	mBodyInterface->CreateAndAddSoftBody(cube, EActivation::Activate);
 	mBodyInterface->CreateAndAddSoftBody(cube, EActivation::Activate);
 
 

+ 1 - 1
Samples/Tests/SoftBody/SoftBodyUpdatePositionTest.cpp

@@ -20,7 +20,7 @@ void SoftBodyUpdatePositionTest::Initialize()
 	CreateFloor();
 	CreateFloor();
 
 
 	// Bodies with various settings for 'make rotation identity' and 'update position'
 	// Bodies with various settings for 'make rotation identity' and 'update position'
-	SoftBodyCreationSettings sphere(SoftBodyCreator::CreateCube(), RVec3::sZero(), Quat::sRotation(Vec3::sReplicate(1.0f / sqrt(3.0f)), 0.25f * JPH_PI), Layers::MOVING);
+	SoftBodyCreationSettings sphere(SoftBodySharedSettings::sCreateCube(5, 0.5f), RVec3::sZero(), Quat::sRotation(Vec3::sReplicate(1.0f / sqrt(3.0f)), 0.25f * JPH_PI), Layers::MOVING);
 
 
 	for (int update_position = 0; update_position < 2; ++update_position)
 	for (int update_position = 0; update_position < 2; ++update_position)
 		for (int make_rotation_identity = 0; make_rotation_identity < 2; ++make_rotation_identity)
 		for (int make_rotation_identity = 0; make_rotation_identity < 2; ++make_rotation_identity)

+ 0 - 143
Samples/Utils/SoftBodyCreator.cpp

@@ -67,149 +67,6 @@ Ref<SoftBodySharedSettings> CreateClothWithFixatedCorners(uint inGridSizeX, uint
 	return CreateCloth(inGridSizeX, inGridSizeZ, inGridSpacing, inv_mass);
 	return CreateCloth(inGridSizeX, inGridSizeZ, inGridSpacing, inv_mass);
 }
 }
 
 
-Ref<SoftBodySharedSettings> CreateCube(uint inGridSize, float inGridSpacing)
-{
-	const Vec3 cOffset = Vec3::sReplicate(-0.5f * inGridSpacing * (inGridSize - 1));
-
-	// Create settings
-	SoftBodySharedSettings *settings = new SoftBodySharedSettings;
-	for (uint z = 0; z < inGridSize; ++z)
-		for (uint y = 0; y < inGridSize; ++y)
-			for (uint x = 0; x < inGridSize; ++x)
-			{
-				SoftBodySharedSettings::Vertex v;
-				(cOffset + Vec3::sReplicate(inGridSpacing) * Vec3(float(x), float(y), float(z))).StoreFloat3(&v.mPosition);
-				settings->mVertices.push_back(v);
-			}
-
-	// Function to get the vertex index of a point on the cloth
-	auto vertex_index = [inGridSize](uint inX, uint inY, uint inZ) -> uint
-	{
-		return inX + inY * inGridSize + inZ * inGridSize * inGridSize;
-	};
-
-	// Create edges
-	for (uint z = 0; z < inGridSize; ++z)
-		for (uint y = 0; y < inGridSize; ++y)
-			for (uint x = 0; x < inGridSize; ++x)
-			{
-				SoftBodySharedSettings::Edge e;
-				e.mVertex[0] = vertex_index(x, y, z);
-				if (x < inGridSize - 1)
-				{
-					e.mVertex[1] = vertex_index(x + 1, y, z);
-					settings->mEdgeConstraints.push_back(e);
-				}
-				if (y < inGridSize - 1)
-				{
-					e.mVertex[1] = vertex_index(x, y + 1, z);
-					settings->mEdgeConstraints.push_back(e);
-				}
-				if (z < inGridSize - 1)
-				{
-					e.mVertex[1] = vertex_index(x, y, z + 1);
-					settings->mEdgeConstraints.push_back(e);
-				}
-			}
-	settings->CalculateEdgeLengths();
-
-	// Tetrahedrons to fill a cube
-	const int tetra_indices[6][4][3] = {
-		{ {0, 0, 0}, {0, 1, 1}, {0, 0, 1}, {1, 1, 1} },
-		{ {0, 0, 0}, {0, 1, 0}, {0, 1, 1}, {1, 1, 1} },
-		{ {0, 0, 0}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1} },
-		{ {0, 0, 0}, {1, 0, 1}, {1, 0, 0}, {1, 1, 1} },
-		{ {0, 0, 0}, {1, 1, 0}, {0, 1, 0}, {1, 1, 1} },
-		{ {0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {1, 1, 1} }
-	};
-
-	// Create volume constraints
-	for (uint z = 0; z < inGridSize - 1; ++z)
-		for (uint y = 0; y < inGridSize - 1; ++y)
-			for (uint x = 0; x < inGridSize - 1; ++x)
-				for (uint t = 0; t < 6; ++t)
-				{
-					SoftBodySharedSettings::Volume v;
-					for (uint i = 0; i < 4; ++i)
-						v.mVertex[i] = vertex_index(x + tetra_indices[t][i][0], y + tetra_indices[t][i][1], z + tetra_indices[t][i][2]);
-					settings->mVolumeConstraints.push_back(v);
-				}
-
-	settings->CalculateVolumeConstraintVolumes();
-
-	// Create faces
-	for (uint y = 0; y < inGridSize - 1; ++y)
-		for (uint x = 0; x < inGridSize - 1; ++x)
-		{
-			SoftBodySharedSettings::Face f;
-
-			// Face 1
-			f.mVertex[0] = vertex_index(x, y, 0);
-			f.mVertex[1] = vertex_index(x, y + 1, 0);
-			f.mVertex[2] = vertex_index(x + 1, y + 1, 0);
-			settings->AddFace(f);
-
-			f.mVertex[1] = vertex_index(x + 1, y + 1, 0);
-			f.mVertex[2] = vertex_index(x + 1, y, 0);
-			settings->AddFace(f);
-
-			// Face 2
-			f.mVertex[0] = vertex_index(x, y, inGridSize - 1);
-			f.mVertex[1] = vertex_index(x + 1, y + 1, inGridSize - 1);
-			f.mVertex[2] = vertex_index(x, y + 1, inGridSize - 1);
-			settings->AddFace(f);
-
-			f.mVertex[1] = vertex_index(x + 1, y, inGridSize - 1);
-			f.mVertex[2] = vertex_index(x + 1, y + 1, inGridSize - 1);
-			settings->AddFace(f);
-
-			// Face 3
-			f.mVertex[0] = vertex_index(x, 0, y);
-			f.mVertex[1] = vertex_index(x + 1, 0, y + 1);
-			f.mVertex[2] = vertex_index(x, 0, y + 1);
-			settings->AddFace(f);
-
-			f.mVertex[1] = vertex_index(x + 1, 0, y);
-			f.mVertex[2] = vertex_index(x + 1, 0, y + 1);
-			settings->AddFace(f);
-
-			// Face 4
-			f.mVertex[0] = vertex_index(x, inGridSize - 1, y);
-			f.mVertex[1] = vertex_index(x, inGridSize - 1, y + 1);
-			f.mVertex[2] = vertex_index(x + 1, inGridSize - 1, y + 1);
-			settings->AddFace(f);
-
-			f.mVertex[1] = vertex_index(x + 1, inGridSize - 1, y + 1);
-			f.mVertex[2] = vertex_index(x + 1, inGridSize - 1, y);
-			settings->AddFace(f);
-
-			// Face 5
-			f.mVertex[0] = vertex_index(0, x, y);
-			f.mVertex[1] = vertex_index(0, x, y + 1);
-			f.mVertex[2] = vertex_index(0, x + 1, y + 1);
-			settings->AddFace(f);
-
-			f.mVertex[1] = vertex_index(0, x + 1, y + 1);
-			f.mVertex[2] = vertex_index(0, x + 1, y);
-			settings->AddFace(f);
-
-			// Face 6
-			f.mVertex[0] = vertex_index(inGridSize - 1, x, y);
-			f.mVertex[1] = vertex_index(inGridSize - 1, x + 1, y + 1);
-			f.mVertex[2] = vertex_index(inGridSize - 1, x, y + 1);
-			settings->AddFace(f);
-
-			f.mVertex[1] = vertex_index(inGridSize - 1, x + 1, y);
-			f.mVertex[2] = vertex_index(inGridSize - 1, x + 1, y + 1);
-			settings->AddFace(f);
-		}
-
-	// Optimize the settings
-	settings->Optimize();
-
-	return settings;
-}
-
 Ref<SoftBodySharedSettings> CreateSphere(float inRadius, uint inNumTheta, uint inNumPhi, SoftBodySharedSettings::EBendType inBendType, const SoftBodySharedSettings::VertexAttributes &inVertexAttributes)
 Ref<SoftBodySharedSettings> CreateSphere(float inRadius, uint inNumTheta, uint inNumPhi, SoftBodySharedSettings::EBendType inBendType, const SoftBodySharedSettings::VertexAttributes &inVertexAttributes)
 {
 {
 	// Create settings
 	// Create settings

+ 0 - 5
Samples/Utils/SoftBodyCreator.h

@@ -18,11 +18,6 @@ namespace SoftBodyCreator
 	/// Same as above but fixates the corners of the cloth
 	/// Same as above but fixates the corners of the cloth
 	Ref<SoftBodySharedSettings>	CreateClothWithFixatedCorners(uint inGridSizeX = 30, uint inGridSizeZ = 30, float inGridSpacing = 0.75f);
 	Ref<SoftBodySharedSettings>	CreateClothWithFixatedCorners(uint inGridSizeX = 30, uint inGridSizeZ = 30, float inGridSpacing = 0.75f);
 
 
-	/// Create a cube
-	/// @param inGridSize Number of points along each axis
-	/// @param inGridSpacing Distance between points
-	Ref<SoftBodySharedSettings>	CreateCube(uint inGridSize = 5, float inGridSpacing = 0.5f);
-
 	/// Create a hollow sphere
 	/// Create a hollow sphere
 	/// @param inRadius Radius of the sphere
 	/// @param inRadius Radius of the sphere
 	/// @param inNumTheta Number of segments in the theta direction
 	/// @param inNumTheta Number of segments in the theta direction

+ 55 - 0
UnitTests/Physics/SoftBodyTests.cpp

@@ -8,6 +8,7 @@
 #include <Jolt/Physics/SoftBody/SoftBodySharedSettings.h>
 #include <Jolt/Physics/SoftBody/SoftBodySharedSettings.h>
 #include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
 #include <Jolt/Physics/SoftBody/SoftBodyCreationSettings.h>
 #include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
 #include <Jolt/Physics/SoftBody/SoftBodyMotionProperties.h>
+#include <Jolt/Physics/Collision/Shape/BoxShape.h>
 
 
 TEST_SUITE("SoftBodyTests")
 TEST_SUITE("SoftBodyTests")
 {
 {
@@ -90,4 +91,58 @@ TEST_SUITE("SoftBodyTests")
 			}
 			}
 		}
 		}
 	}
 	}
+
+	// Test that applying a force to a soft body and rigid body of the same mass has the same effect
+	TEST_CASE("TestApplyForce")
+	{
+		PhysicsTestContext c;
+		PhysicsSystem *s = c.GetSystem();
+		BodyInterface &bi = s->GetBodyInterface();
+
+		// Soft body cube
+		SoftBodyCreationSettings sb_box_settings(SoftBodySharedSettings::sCreateCube(6, 0.2f), RVec3::sZero(), Quat::sIdentity(), Layers::MOVING);
+		sb_box_settings.mGravityFactor = 0.0f;
+		sb_box_settings.mLinearDamping = 0.0f;
+		Body &sb_box = *bi.CreateSoftBody(sb_box_settings);
+		BodyID sb_id = sb_box.GetID();
+		bi.AddBody(sb_id, EActivation::Activate);
+		constexpr float cMass = 216; // 6 * 6 * 6 * 1 kg
+		CHECK_APPROX_EQUAL(sb_box.GetMotionProperties()->GetInverseMass(), 1.0f / cMass);
+
+		// Rigid body cube of same size and mass
+		const RVec3 cRBBoxPos(0, 2, 0);
+		BodyCreationSettings rb_box_settings(new BoxShape(Vec3::sReplicate(0.5f)), cRBBoxPos, Quat::sIdentity(), EMotionType::Dynamic, Layers::MOVING);
+		rb_box_settings.mGravityFactor = 0.0f;
+		rb_box_settings.mLinearDamping = 0.0f;
+		rb_box_settings.mOverrideMassProperties = EOverrideMassProperties::CalculateInertia;
+		rb_box_settings.mMassPropertiesOverride.mMass = cMass;
+		Body &rb_box = *bi.CreateBody(rb_box_settings);
+		BodyID rb_id = rb_box.GetID();
+		bi.AddBody(rb_id, EActivation::Activate);
+
+		// Simulate for 3 seconds while applying the same force
+		constexpr int cNumSteps = 180;
+		const Vec3 cForce(10000.0f, 0, 0);
+		for (int i = 0; i < cNumSteps; ++i)
+		{
+			bi.AddForce(sb_id, cForce, EActivation::Activate);
+			bi.AddForce(rb_id, cForce, EActivation::Activate);
+			c.SimulateSingleStep();
+		}
+
+		// Check that the rigid body moved as expected
+		const float cTotalTime = cNumSteps * c.GetStepDeltaTime();
+		const Vec3 cAcceleration = cForce / cMass;
+		const RVec3 cExpectedPos = c.PredictPosition(cRBBoxPos, Vec3::sZero(), cAcceleration, cTotalTime);
+		CHECK_APPROX_EQUAL(rb_box.GetPosition(), cExpectedPos);
+		const Vec3 cExpectedVel = cAcceleration * cTotalTime;
+		CHECK_APPROX_EQUAL(rb_box.GetLinearVelocity(), cExpectedVel, 1.0e-3f);
+		CHECK_APPROX_EQUAL(rb_box.GetAngularVelocity(), Vec3::sZero());
+
+		// Check that the soft body moved within 1% of that
+		const RVec3 cExpectedPosSB = cExpectedPos - cRBBoxPos;
+		CHECK_APPROX_EQUAL(sb_box.GetPosition(), cExpectedPosSB, 0.01f * cExpectedPosSB.Length());
+		CHECK_APPROX_EQUAL(sb_box.GetLinearVelocity(), cExpectedVel, 2.0e-3f);
+		CHECK_APPROX_EQUAL(sb_box.GetAngularVelocity(), Vec3::sZero(), 0.01f);
+	}
 }
 }