diff --git a/other/GodotDemoSoftBodies/.gitattributes b/other/GodotDemoSoftBodies/.gitattributes
new file mode 100644
index 00000000..8ad74f78
--- /dev/null
+++ b/other/GodotDemoSoftBodies/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf
diff --git a/other/GodotDemoSoftBodies/.gitignore b/other/GodotDemoSoftBodies/.gitignore
new file mode 100644
index 00000000..bc1dc819
--- /dev/null
+++ b/other/GodotDemoSoftBodies/.gitignore
@@ -0,0 +1,3 @@
+# Godot 4+ specific ignores
+.godot/
+.idea
diff --git a/other/GodotDemoSoftBodies/JitterGodot.csproj b/other/GodotDemoSoftBodies/JitterGodot.csproj
new file mode 100644
index 00000000..79b7b3dc
--- /dev/null
+++ b/other/GodotDemoSoftBodies/JitterGodot.csproj
@@ -0,0 +1,9 @@
+
+
+ net8.0
+ true
+
+
+
+
+
diff --git a/other/GodotDemoSoftBodies/JitterGodot.sln b/other/GodotDemoSoftBodies/JitterGodot.sln
new file mode 100644
index 00000000..b67e3c29
--- /dev/null
+++ b/other/GodotDemoSoftBodies/JitterGodot.sln
@@ -0,0 +1,19 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JitterGodot", "JitterGodot.csproj", "{D95E4F8D-CDCA-48E7-95EC-3EBBD7A0431A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ ExportDebug|Any CPU = ExportDebug|Any CPU
+ ExportRelease|Any CPU = ExportRelease|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D95E4F8D-CDCA-48E7-95EC-3EBBD7A0431A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D95E4F8D-CDCA-48E7-95EC-3EBBD7A0431A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D95E4F8D-CDCA-48E7-95EC-3EBBD7A0431A}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
+ {D95E4F8D-CDCA-48E7-95EC-3EBBD7A0431A}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
+ {D95E4F8D-CDCA-48E7-95EC-3EBBD7A0431A}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
+ {D95E4F8D-CDCA-48E7-95EC-3EBBD7A0431A}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/other/GodotDemoSoftBodies/Program.cs b/other/GodotDemoSoftBodies/Program.cs
new file mode 100644
index 00000000..eae6eff0
--- /dev/null
+++ b/other/GodotDemoSoftBodies/Program.cs
@@ -0,0 +1,454 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Godot;
+using Jitter2;
+using Jitter2.Collision.Shapes;
+using Jitter2.Dynamics;
+using Jitter2.Dynamics.Constraints;
+using Jitter2.LinearMath;
+using Jitter2.SoftBodies;
+
+public static class Conversion
+{
+ public static Vector3 FromJitter(in JVector vec) => new Vector3(vec.X, vec.Y, vec.Z);
+}
+
+public class CubedSoftBody(World world) : SoftBody(world)
+{
+ public int MaterialIndex { get; set; } = 0;
+
+ public struct BuildVertex(int x, int y, int z)
+ {
+ public int X = x, Y = y, Z = z;
+
+ public static BuildVertex operator +(BuildVertex a, BuildVertex b)
+ => new BuildVertex(a.X + b.X, a.Y + b.Y, a.Z + b.Z);
+ }
+
+ public struct BuildTetrahedron(int a, int b, int c, int d)
+ {
+ public int A = a, B = b, C = c, D = d;
+ }
+
+ public class BuildQuad(int a, int b, int c, int d, bool diagLeft = false) : IEquatable
+ {
+ public int A = a, B = b, C = c, D = d;
+ public bool DiagLeft = diagLeft;
+
+ public Vector2 UvA;
+ public Vector2 UvB;
+ public Vector2 UvC;
+ public Vector2 UvD;
+
+ public void SetUvCoordinates(BuildVertex[] vertices)
+ {
+ int minX = Math.Min(Math.Min(Math.Min(vertices[A].X, vertices[B].X), vertices[C].X), vertices[D].X);
+ int minY = Math.Min(Math.Min(Math.Min(vertices[A].Y, vertices[B].Y), vertices[C].Y), vertices[D].Y);
+ int minZ = Math.Min(Math.Min(Math.Min(vertices[A].Z, vertices[B].Z), vertices[C].Z), vertices[D].Z);
+
+ if (vertices[A].X == vertices[B].X && vertices[A].X == vertices[C].X && vertices[A].X == vertices[D].X)
+ {
+ UvA = new Vector2(vertices[A].Y - minY, vertices[A].Z - minZ) * 0.5f;
+ UvB = new Vector2(vertices[B].Y - minY, vertices[B].Z - minZ) * 0.5f;
+ UvC = new Vector2(vertices[C].Y - minY, vertices[C].Z - minZ) * 0.5f;
+ UvD = new Vector2(vertices[D].Y - minY, vertices[D].Z - minZ) * 0.5f;
+ }
+ else if (vertices[A].Y == vertices[B].Y && vertices[A].Y == vertices[C].Y && vertices[A].Y == vertices[D].Y)
+ {
+ UvA = new Vector2(vertices[A].X - minX, vertices[A].Z - minZ) * 0.5f;
+ UvB = new Vector2(vertices[B].X - minX, vertices[B].Z - minZ) * 0.5f;
+ UvC = new Vector2(vertices[C].X - minX, vertices[C].Z - minZ) * 0.5f;
+ UvD = new Vector2(vertices[D].X - minX, vertices[D].Z - minZ) * 0.5f;
+ }
+ else if (vertices[A].Z == vertices[B].Z && vertices[A].Z == vertices[C].Z && vertices[A].Z == vertices[D].Z)
+ {
+ UvA = new Vector2(vertices[A].X - minX, vertices[A].Y - minY) * 0.5f;
+ UvB = new Vector2(vertices[B].X - minX, vertices[B].Y - minY) * 0.5f;
+ UvC = new Vector2(vertices[C].X - minX, vertices[C].Y - minY) * 0.5f;
+ UvD = new Vector2(vertices[D].X - minX, vertices[D].Y - minY) * 0.5f;
+ }
+ else
+ {
+ throw new InvalidOperationException();
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is BuildQuad other && Equals(other);
+ }
+
+ public bool Equals(BuildQuad other)
+ {
+ bool bA = (A == other.A || A == other.B || A == other.C || A == other.D);
+ bool bB = (B == other.A || B == other.B || B == other.C || B == other.D);
+ bool bC = (C == other.A || C == other.B || C == other.C || C == other.D);
+ bool bD = (D == other.A || D == other.B || D == other.C || D == other.D);
+ return bA && bB && bC && bD;
+ }
+
+ public override int GetHashCode()
+ {
+ return (A * B * C * D) + (A + B + C + D);
+ }
+ }
+
+ private bool isFinished = false;
+
+ private readonly Dictionary vertexIndices = new();
+
+ private List tetrahedra = new();
+ private List cubeCenters = new();
+
+ public HashSet Quads { get; } = new();
+
+ private BuildVertex[] cubeVertices = new[]
+ {
+ new BuildVertex(+1, -1, +1),
+ new BuildVertex(+1, -1, -1),
+ new BuildVertex(-1, -1, -1),
+ new BuildVertex(-1, -1, +1),
+ new BuildVertex(+1, +1, +1),
+ new BuildVertex(+1, +1, -1),
+ new BuildVertex(-1, +1, -1),
+ new BuildVertex(-1, +1, +1)
+ };
+
+ private int PushVertex(BuildVertex vertex)
+ {
+ if (vertexIndices.TryGetValue(vertex, out int result))
+ return result;
+ result = vertexIndices.Count;
+ vertexIndices.Add(vertex, result);
+ return result;
+ }
+
+ public void AddCube(int x, int y, int z)
+ {
+ if (isFinished) throw new InvalidOperationException();
+
+ var origin = new BuildVertex(x, y, z);
+
+ cubeCenters.Add(origin);
+
+ Span idx = stackalloc int[8];
+
+ for (int i = 0; i < 8; i++)
+ {
+ idx[i] = PushVertex(origin + cubeVertices[i]);
+ }
+
+ tetrahedra.Add(new BuildTetrahedron(idx[0], idx[1], idx[5], idx[2]));
+ tetrahedra.Add(new BuildTetrahedron(idx[5], idx[2], idx[6], idx[7]));
+ tetrahedra.Add(new BuildTetrahedron(idx[0], idx[3], idx[2], idx[7]));
+ tetrahedra.Add(new BuildTetrahedron(idx[4], idx[0], idx[5], idx[7]));
+ tetrahedra.Add(new BuildTetrahedron(idx[0], idx[2], idx[5], idx[7]));
+
+ void PushQuad(BuildQuad bq)
+ {
+ if (!Quads.Add(bq)) Quads.Remove(bq);
+ }
+
+ PushQuad(new BuildQuad(idx[5], idx[4], idx[7], idx[6], true)); // top
+ PushQuad(new BuildQuad(idx[2], idx[3], idx[0], idx[1], false)); // bottom
+ PushQuad(new BuildQuad(idx[6], idx[7], idx[3], idx[2], true)); // left
+ PushQuad(new BuildQuad(idx[1], idx[0], idx[4], idx[5], false)); // right
+ PushQuad(new BuildQuad(idx[3], idx[7], idx[4], idx[0], true)); // front
+ PushQuad(new BuildQuad(idx[1], idx[5], idx[6], idx[2], false)); // back
+ }
+
+ public void Finalize(float scale = 0.25f)
+ {
+ if (isFinished) throw new InvalidOperationException();
+
+ BuildVertex[] vertices = vertexIndices.OrderBy(pair => pair.Value).Select(pair => pair.Key).ToArray();
+
+ foreach (var quad in Quads)
+ {
+ quad.SetUvCoordinates(vertices);
+ }
+
+ foreach (var vertex in vertices)
+ {
+ var rb = world.CreateRigidBody();
+ rb.SetMassInertia(JMatrix.Zero, 8f, true);
+ rb.Position = new JVector(vertex.X, vertex.Y, vertex.Z) * scale;
+ this.Vertices.Add(rb);
+ }
+
+ foreach (var trh in tetrahedra)
+ {
+ SoftBodyTetrahedron sbt = new(this,
+ Vertices[trh.A], Vertices[trh.B],
+ Vertices[trh.C], Vertices[trh.D]);
+
+ world.AddShape(sbt);
+ this.Shapes.Add(sbt);
+ }
+
+ for (int i = 0; i < cubeCenters.Count; i++)
+ {
+ var centerCube = world.CreateRigidBody();
+
+ centerCube.Position = new JVector(cubeCenters[i].X, cubeCenters[i].Y, cubeCenters[i].Z) * scale;
+ centerCube.SetMassInertia(JMatrix.Identity * 1f, 1f);
+
+ List ct = new(8);
+
+ ct.Add(world.CreateConstraint(centerCube, Vertices[tetrahedra[5 * i + 0].A])); // 0
+ ct.Add(world.CreateConstraint(centerCube, Vertices[tetrahedra[5 * i + 0].B])); // 1
+ ct.Add(world.CreateConstraint(centerCube, Vertices[tetrahedra[5 * i + 0].C])); // 5
+ ct.Add(world.CreateConstraint(centerCube, Vertices[tetrahedra[5 * i + 0].D])); // 2
+ ct.Add(world.CreateConstraint(centerCube, Vertices[tetrahedra[5 * i + 1].C])); // 6
+ ct.Add(world.CreateConstraint(centerCube, Vertices[tetrahedra[5 * i + 1].D])); // 7
+ ct.Add(world.CreateConstraint(centerCube, Vertices[tetrahedra[5 * i + 2].B])); // 3
+ ct.Add(world.CreateConstraint(centerCube, Vertices[tetrahedra[5 * i + 3].A])); // 4
+
+ foreach (var c in ct)
+ {
+ c.Initialize(c.Body2.Position);
+ c.Softness = 0.1f;
+ c.Bias = 0.3f;
+ }
+ }
+
+ isFinished = true;
+ }
+}
+
+public partial class JitterSoftBodyCubeDrawer : MeshInstance3D
+{
+ private ImmediateMesh immediateMesh = new();
+
+ private List cubes = new();
+
+ private Material[] materials = new Material[5];
+
+ public void Clear()
+ {
+ foreach (var cube in cubes)
+ {
+ cube.Destroy();
+ }
+
+ cubes.Clear();
+ }
+
+ public void AddCubedSoftBody(CubedSoftBody body)
+ {
+ cubes.Add(body);
+ }
+
+ public override void _Ready()
+ {
+ this.Mesh = immediateMesh;
+
+ var mat = ResourceLoader.Load("res://box.material");
+
+ for (int i = 0; i < 5; i++)
+ {
+ materials[i] = mat.Duplicate(false) as Material;
+ }
+
+ ((StandardMaterial3D)materials[0]).AlbedoColor = new Color(0, 0.94f, 0.94f); // I
+ ((StandardMaterial3D)materials[1]).AlbedoColor = new Color(0.94f, 0.94f, 0); // O
+ ((StandardMaterial3D)materials[2]).AlbedoColor = new Color(0, 0, 0.94f); // L
+ ((StandardMaterial3D)materials[3]).AlbedoColor = new Color(0.94f ,0, 0); // Z
+ ((StandardMaterial3D)materials[4]).AlbedoColor = new Color(0.63f, 0, 0.94f); // T
+
+ base._Ready();
+ }
+
+ public override void _Process(double delta)
+ {
+ int surfaces = 0;
+
+ immediateMesh.ClearSurfaces();
+
+ foreach (var cube in cubes)
+ {
+ immediateMesh.SurfaceBegin(Mesh.PrimitiveType.Triangles);
+
+ foreach (var quad in cube.Quads)
+ {
+ if (quad.DiagLeft)
+ {
+ immediateMesh.SurfaceSetUV(quad.UvA);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.A].Position));
+ immediateMesh.SurfaceSetUV(quad.UvB);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.B].Position));
+ immediateMesh.SurfaceSetUV(quad.UvC);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.C].Position));
+ immediateMesh.SurfaceSetUV(quad.UvA);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.A].Position));
+ immediateMesh.SurfaceSetUV(quad.UvC);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.C].Position));
+ immediateMesh.SurfaceSetUV(quad.UvD);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.D].Position));
+ }
+ else
+ {
+ immediateMesh.SurfaceSetUV(quad.UvA);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.A].Position));
+ immediateMesh.SurfaceSetUV(quad.UvB);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.B].Position));
+ immediateMesh.SurfaceSetUV(quad.UvD);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.D].Position));
+ immediateMesh.SurfaceSetUV(quad.UvB);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.B].Position));
+ immediateMesh.SurfaceSetUV(quad.UvC);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.C].Position));
+ immediateMesh.SurfaceSetUV(quad.UvD);
+ immediateMesh.SurfaceAddVertex(Conversion.FromJitter(cube.Vertices[quad.D].Position));
+ }
+ }
+
+ immediateMesh.SurfaceEnd();
+ immediateMesh.SurfaceSetMaterial(surfaces++, materials[cube.MaterialIndex]);
+ }
+
+ base._Process(delta);
+ }
+}
+
+public partial class Program : Node3D
+{
+ private World world = null!;
+
+ private JitterSoftBodyCubeDrawer softBodyDrawer = new();
+
+ private void Add2x2x2(CubedSoftBody csb, int x, int y, int z)
+ {
+ csb.AddCube(x + 1, y + 1, z + 1);
+ csb.AddCube(x + 1, y + 1, z - 1);
+ csb.AddCube(x - 1, y + 1, z + 1);
+ csb.AddCube(x - 1, y + 1, z - 1);
+ csb.AddCube(x + 1, y - 1, z + 1);
+ csb.AddCube(x + 1, y - 1, z - 1);
+ csb.AddCube(x - 1, y - 1, z + 1);
+ csb.AddCube(x - 1, y - 1, z - 1);
+ }
+
+ private void AddI(int x, int y, int z)
+ {
+ CubedSoftBody csb = new CubedSoftBody(world);
+
+ Add2x2x2(csb, x, y + 0, z);
+ Add2x2x2(csb, x, y + 4, z);
+ Add2x2x2(csb, x, y + 8, z);
+ Add2x2x2(csb, x, y + 12, z);
+
+ csb.Finalize();
+ csb.MaterialIndex = 0;
+
+ softBodyDrawer.AddCubedSoftBody(csb);
+ }
+
+ private void AddO(int x, int y, int z)
+ {
+ CubedSoftBody csb = new CubedSoftBody(world);
+
+ Add2x2x2(csb, x + 0, y + 0, z);
+ Add2x2x2(csb, x + 0, y + 4, z);
+ Add2x2x2(csb, x + 4, y + 0, z);
+ Add2x2x2(csb, x + 4, y + 4, z);
+
+ csb.Finalize();
+ csb.MaterialIndex = 1;
+
+ softBodyDrawer.AddCubedSoftBody(csb);
+ }
+
+ private void AddL(int x, int y, int z)
+ {
+ CubedSoftBody csb = new CubedSoftBody(world);
+
+ Add2x2x2(csb, x + 0, y + 0, z);
+ Add2x2x2(csb, x + 0, y + 4, z);
+ Add2x2x2(csb, x + 4, y + 4, z);
+ Add2x2x2(csb, x + 8, y + 4, z);
+
+ csb.Finalize();
+ csb.MaterialIndex = 2;
+
+ softBodyDrawer.AddCubedSoftBody(csb);
+ }
+
+ private void AddZ(int x, int y, int z)
+ {
+ CubedSoftBody csb = new CubedSoftBody(world);
+
+ Add2x2x2(csb, x + 0, y + 0, z);
+ Add2x2x2(csb, x + 4, y + 0, z);
+ Add2x2x2(csb, x + 4, y + 4, z);
+ Add2x2x2(csb, x + 8, y + 4, z);
+
+ csb.Finalize();
+ csb.MaterialIndex = 3;
+
+ softBodyDrawer.AddCubedSoftBody(csb);
+ }
+
+ private void AddT(int x, int y, int z)
+ {
+ CubedSoftBody csb = new CubedSoftBody(world);
+
+ Add2x2x2(csb, x + 0, y + 0, z);
+ Add2x2x2(csb, x + 4, y + 0, z);
+ Add2x2x2(csb, x + 8, y + 0, z);
+ Add2x2x2(csb, x + 4, y + 4, z);
+
+ csb.Finalize();
+ csb.MaterialIndex = 4;
+
+ softBodyDrawer.AddCubedSoftBody(csb);
+ }
+
+ public override void _Ready()
+ {
+ var button = new Button();
+ button.Pressed += ResetScene;
+ button.Position = new Vector2(4, 4);
+ button.Text = "Reset scene";
+
+ AddChild(button);
+ AddChild(softBodyDrawer);
+
+ world = new World();
+ ResetScene();
+ }
+
+ private void ResetScene()
+ {
+ softBodyDrawer.Clear();
+
+ world.Clear();
+
+ // floor shape
+ RigidBody floor = world.CreateRigidBody();
+ floor.AddShape(new BoxShape(20));
+ floor.Position = new JVector(0, -10, 0);
+ floor.IsStatic = true;
+
+ world.DynamicTree.Filter = DynamicTreeCollisionFilter.Filter;
+ world.BroadPhaseFilter = new BroadPhaseCollisionFilter(world);
+
+ for (int i = 0; i < 15; i++)
+ {
+ AddI(0, (5 * i + 0) * 30, 0);
+ AddL(0, (5 * i + 1) * 30, 0);
+ AddZ(0, (5 * i + 2) * 30, 0);
+ AddT(0, (5 * i + 3) * 30, 0);
+ AddO(0, (5 * i + 4) * 30, 0);
+ }
+
+ world.NumberSubsteps = 4;
+ world.SolverIterations = 4;
+ }
+
+ public override void _Process(double delta)
+ {
+ world.Step(1.0f / 100.0f, true);
+ }
+}
diff --git a/other/GodotDemoSoftBodies/README.md b/other/GodotDemoSoftBodies/README.md
new file mode 100644
index 00000000..05479a8c
--- /dev/null
+++ b/other/GodotDemoSoftBodies/README.md
@@ -0,0 +1,5 @@
+## Jitter2 SoftBodies in Godot
+
+Small demo to showing Jitter2 Softbodies in Godot.
+
+Get Godot (.NET, Version >= 4.2.1) from https://godotengine.org/. Open Godot and select "project.godot".
diff --git a/other/GodotDemoSoftBodies/assets/texture_08.png b/other/GodotDemoSoftBodies/assets/texture_08.png
new file mode 100644
index 00000000..0751385c
Binary files /dev/null and b/other/GodotDemoSoftBodies/assets/texture_08.png differ
diff --git a/other/GodotDemoSoftBodies/assets/texture_08.png.import b/other/GodotDemoSoftBodies/assets/texture_08.png.import
new file mode 100644
index 00000000..b2604b81
--- /dev/null
+++ b/other/GodotDemoSoftBodies/assets/texture_08.png.import
@@ -0,0 +1,35 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://couisnncdt24n"
+path.s3tc="res://.godot/imported/texture_08.png-55422b6c9181f85f6ecd7904cb7603ac.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://assets/texture_08.png"
+dest_files=["res://.godot/imported/texture_08.png-55422b6c9181f85f6ecd7904cb7603ac.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/other/GodotDemoSoftBodies/box.material b/other/GodotDemoSoftBodies/box.material
new file mode 100644
index 00000000..20c76d99
Binary files /dev/null and b/other/GodotDemoSoftBodies/box.material differ
diff --git a/other/GodotDemoSoftBodies/icon.svg b/other/GodotDemoSoftBodies/icon.svg
new file mode 100644
index 00000000..b370ceb7
--- /dev/null
+++ b/other/GodotDemoSoftBodies/icon.svg
@@ -0,0 +1 @@
+
diff --git a/other/GodotDemoSoftBodies/icon.svg.import b/other/GodotDemoSoftBodies/icon.svg.import
new file mode 100644
index 00000000..aa536579
--- /dev/null
+++ b/other/GodotDemoSoftBodies/icon.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bj3qla3sx2dm8"
+path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://icon.svg"
+dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/other/GodotDemoSoftBodies/main_scene.tscn b/other/GodotDemoSoftBodies/main_scene.tscn
new file mode 100644
index 00000000..1f1be909
--- /dev/null
+++ b/other/GodotDemoSoftBodies/main_scene.tscn
@@ -0,0 +1,49 @@
+[gd_scene load_steps=9 format=3 uid="uid://coyqv4677vx2l"]
+
+[ext_resource type="Script" path="res://Program.cs" id="1_q0tim"]
+[ext_resource type="Material" uid="uid://bnmu1bn4m70pu" path="res://box.material" id="4_umvs4"]
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_bdasd"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6v48f"]
+albedo_color = Color(0.694118, 0.756863, 0.976471, 1)
+uv1_scale = Vector3(10, 10, 10)
+
+[sub_resource type="PhysicalSkyMaterial" id="PhysicalSkyMaterial_hoxee"]
+ground_color = Color(0.627451, 0.607843, 0.890196, 1)
+energy_multiplier = 4.35
+
+[sub_resource type="Sky" id="Sky_u73ht"]
+sky_material = SubResource("PhysicalSkyMaterial_hoxee")
+
+[sub_resource type="Environment" id="Environment_ekotd"]
+background_mode = 2
+sky = SubResource("Sky_u73ht")
+ambient_light_energy = 2.86
+
+[sub_resource type="BoxMesh" id="BoxMesh_0u3bq"]
+
+[node name="Node3D" type="Node3D"]
+script = ExtResource("1_q0tim")
+
+[node name="Floor" type="MeshInstance3D" parent="."]
+transform = Transform3D(20, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0, 0)
+mesh = SubResource("PlaneMesh_bdasd")
+surface_material_override/0 = SubResource("StandardMaterial3D_6v48f")
+
+[node name="Camera3D" type="Camera3D" parent="."]
+transform = Transform3D(0.956687, -0.036487, 0.288824, 0, 0.992115, 0.125333, -0.29112, -0.119904, 0.949143, 4.44888, 6.19976, 15.391)
+visible = false
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_ekotd")
+
+[node name="TestCube" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.49664, 0)
+visible = false
+mesh = SubResource("BoxMesh_0u3bq")
+surface_material_override/0 = ExtResource("4_umvs4")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(0.574853, -0.657468, 0.487113, 0.0171256, 0.604843, 0.796161, -0.818077, -0.449333, 0.358955, 7.26371, 4.83944, 0)
+shadow_enabled = true
diff --git a/other/GodotDemoSoftBodies/project.godot b/other/GodotDemoSoftBodies/project.godot
new file mode 100644
index 00000000..d05b034f
--- /dev/null
+++ b/other/GodotDemoSoftBodies/project.godot
@@ -0,0 +1,30 @@
+; Engine configuration file.
+; It's best edited using the editor UI and not directly,
+; since the parameters that go here are not all obvious.
+;
+; Format:
+; [section] ; section goes between []
+; param=value ; assign values to parameters
+
+config_version=5
+
+[application]
+
+config/name="JitterGodot"
+run/main_scene="res://main_scene.tscn"
+config/features=PackedStringArray("4.2", "C#", "Forward Plus")
+config/icon="res://icon.svg"
+
+[display]
+
+window/size/viewport_width=1920
+window/size/viewport_height=1200
+
+[dotnet]
+
+project/assembly_name="JitterGodot"
+
+[rendering]
+
+lights_and_shadows/directional_shadow/size=8192
+anti_aliasing/quality/msaa_3d=3