Selaa lähdekoodia

Jobified the soft body update (#716)

* Soft bodies can now be distributed across multiple threads
* CollideSoftBodyVertices can be distributed across multiple threads within a single soft body
* Edge constraints can be distributed across multiple threads within a single soft body
* Updated the architecture document with these new jobs
Jorrit Rouwe 1 vuosi sitten
vanhempi
commit
9a279e7d16
44 muutettua tiedostoa jossa 976 lisäystä ja 371 poistoa
  1. 16 0
      Docs/Architecture.md
  2. 79 23
      Docs/PhysicsSystemUpdate.drawio
  3. 0 0
      Docs/PhysicsSystemUpdate.svg
  4. 12 12
      Jolt/Physics/Collision/Shape/BoxShape.cpp
  5. 1 1
      Jolt/Physics/Collision/Shape/BoxShape.h
  6. 12 12
      Jolt/Physics/Collision/Shape/CapsuleShape.cpp
  7. 1 1
      Jolt/Physics/Collision/Shape/CapsuleShape.h
  8. 2 2
      Jolt/Physics/Collision/Shape/CompoundShape.cpp
  9. 1 1
      Jolt/Physics/Collision/Shape/CompoundShape.h
  10. 8 8
      Jolt/Physics/Collision/Shape/ConvexHullShape.cpp
  11. 1 1
      Jolt/Physics/Collision/Shape/ConvexHullShape.h
  12. 8 8
      Jolt/Physics/Collision/Shape/CylinderShape.cpp
  13. 1 1
      Jolt/Physics/Collision/Shape/CylinderShape.h
  14. 2 2
      Jolt/Physics/Collision/Shape/HeightFieldShape.cpp
  15. 1 1
      Jolt/Physics/Collision/Shape/HeightFieldShape.h
  16. 2 2
      Jolt/Physics/Collision/Shape/MeshShape.cpp
  17. 1 1
      Jolt/Physics/Collision/Shape/MeshShape.h
  18. 2 2
      Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.cpp
  19. 1 1
      Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h
  20. 2 2
      Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp
  21. 1 1
      Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h
  22. 2 2
      Jolt/Physics/Collision/Shape/ScaledShape.cpp
  23. 1 1
      Jolt/Physics/Collision/Shape/ScaledShape.h
  24. 9 9
      Jolt/Physics/Collision/Shape/Shape.cpp
  25. 3 2
      Jolt/Physics/Collision/Shape/Shape.h
  26. 8 8
      Jolt/Physics/Collision/Shape/SphereShape.cpp
  27. 1 1
      Jolt/Physics/Collision/Shape/SphereShape.h
  28. 8 8
      Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp
  29. 1 1
      Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h
  30. 14 14
      Jolt/Physics/Collision/Shape/TriangleShape.cpp
  31. 1 1
      Jolt/Physics/Collision/Shape/TriangleShape.h
  32. 167 28
      Jolt/Physics/PhysicsSystem.cpp
  33. 4 1
      Jolt/Physics/PhysicsSystem.h
  34. 9 1
      Jolt/Physics/PhysicsUpdateContext.h
  35. 340 187
      Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp
  36. 83 4
      Jolt/Physics/SoftBody/SoftBodyMotionProperties.h
  37. 1 1
      Jolt/Physics/SoftBody/SoftBodyShape.cpp
  38. 1 1
      Jolt/Physics/SoftBody/SoftBodyShape.h
  39. 61 0
      Jolt/Physics/SoftBody/SoftBodySharedSettings.cpp
  40. 14 0
      Jolt/Physics/SoftBody/SoftBodySharedSettings.h
  41. 65 0
      Jolt/Physics/SoftBody/SoftBodyUpdateContext.h
  42. 0 1
      Jolt/Physics/SoftBody/SoftBodyVertex.h
  43. 17 18
      Samples/SamplesApp.cpp
  44. 12 0
      Samples/Utils/SoftBodyCreator.cpp

+ 16 - 0
Docs/Architecture.md

@@ -559,3 +559,19 @@ A number of these jobs will run in parallel. Each job takes the next unprocessed
 It will also notify the broad phase of the new body positions / AABBs.
 
 When objects move too little the body will be put to sleep. This is detected by taking the biggest two axis of the local space bounding box of the shape together with the center of mass of the shape (all points in world space) and keep track of 3 bounding spheres for those points over time. If the bounding spheres become too big, the bounding spheres are reset and the timer restarted. When the timer reaches a certain time, the object has is considered non-moving and is put to sleep.
+
+## Soft Body Prepare
+
+If there are any active soft bodies, this job will create the Soft Body Collide, Simulate and Finalize Jobs. It will also create a list of sorted SoftBodyUpdateContext objects that forms the context for those jobs.
+
+## Soft Body Collide
+
+These jobs will do broadphase checks for all of the soft bodies. A thread picks up a single soft body and uses the bounding box of the soft body to find intersecting rigid bodies. Once found, information will be collected about that rigid body so that Simulate can run in parallel.
+
+## Soft Body Simulate
+
+These jobs will do the actual simulation of the soft bodies. They first collide batches of soft body vertices with the rigid bodies found during during the Collide job (multiple threads can work on a single soft body) and then perform the simulation using XPBD (also partially distributing a single soft body on multiple threads).
+
+## Soft Body Finalize
+
+This job writes back all the rigid body velocity changes and updates the positions and velocities of the soft bodies. It can activate/deactivate bodies as needed.

+ 79 - 23
Docs/PhysicsSystemUpdate.drawio

@@ -1,11 +1,11 @@
-<mxfile host="app.diagrams.net" modified="2023-06-30T09:18:50.006Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36" etag="Anb81jEkvc856WIvME0H" version="21.5.0" type="device">
+<mxfile host="Electron" modified="2023-09-24T13:05:17.254Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/21.7.5 Chrome/114.0.5735.289 Electron/25.8.1 Safari/537.36" etag="y67BJ_dSAoWEx4PTNBpq" version="21.7.5" type="device">
   <diagram id="rLFVS3KHCrdhIcSo5p6n" name="Page-1">
-    <mxGraphModel dx="1562" dy="771" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="#FFFFFF" math="0" shadow="0">
+    <mxGraphModel dx="1548" dy="894" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" background="#FFFFFF" math="0" shadow="0">
       <root>
         <mxCell id="0" />
         <mxCell id="1" parent="0" />
         <mxCell id="2" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#d9d9d9;strokeColor=#333333;gradientColor=#FFFFFF;gradientDirection=north;opacity=100.0;gliffyId=319;" parent="1" vertex="1">
-          <mxGeometry x="22.5" y="101.75" width="1727.5" height="478" as="geometry" />
+          <mxGeometry x="22.5" y="101.75" width="2167.5" height="478" as="geometry" />
         </mxCell>
         <mxCell id="4" value="&lt;div style=&#39;width: 93.0px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Apply Gravity &lt;/span&gt;&lt;/div&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;(in batches)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#fff2cc;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=2.0;spacingRight=0;whiteSpace=wrap;gliffyId=3;" parent="1" vertex="1">
           <mxGeometry x="209.8640594482422" y="458.3939208984375" width="100" height="65.1060791015625" as="geometry" />
@@ -59,7 +59,7 @@
           <mxGeometry x="1456.2425537109375" y="136.25" width="100" height="92" as="geometry" />
         </mxCell>
         <mxCell id="14" style="shape=ellipse;perimeter=ellipsePerimeter;shadow=0;strokeWidth=2;fillColor=#000000;strokeColor=#333333;opacity=100.0;gliffyId=100;" parent="1" vertex="1">
-          <mxGeometry x="1720.0025537109375" y="396" width="15" height="15" as="geometry" />
+          <mxGeometry x="2210.0025537109377" y="332.7" width="15" height="15" as="geometry" />
         </mxCell>
         <mxCell id="15" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=114;exitX=1.0;exitY=0.5;exitPerimeter=0;entryX=1.1102230246251565E-16;entryY=0.2928932309150696;entryPerimeter=0;" parent="1" source="77" target="71" edge="1">
           <mxGeometry width="100" height="100" relative="1" as="geometry">
@@ -110,7 +110,7 @@
           </mxGeometry>
         </mxCell>
         <mxCell id="22" value="&lt;div style=&#39;width: 141.96px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Set Body Island Idx&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ffffff;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=3.02;spacingRight=0;whiteSpace=wrap;gliffyId=152;" parent="1" vertex="1">
-          <mxGeometry x="713.6599731445312" y="436.6060791015625" width="151" height="65.1060791015625" as="geometry" />
+          <mxGeometry x="1399.9999731445314" y="436.7460791015625" width="151" height="65.1060791015625" as="geometry" />
         </mxCell>
         <mxCell id="23" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=154;exitX=1.0;exitY=0.5;exitPerimeter=0;entryX=0.0;entryY=0.5;entryPerimeter=0;" parent="1" source="10" target="22" edge="1">
           <mxGeometry width="100" height="100" relative="1" as="geometry">
@@ -120,19 +120,19 @@
             </Array>
           </mxGeometry>
         </mxCell>
-        <mxCell id="24" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=155;edgeStyle=orthogonalEdgeStyle;" parent="1" source="22" target="104" edge="1">
+        <mxCell id="24" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=155;edgeStyle=orthogonalEdgeStyle;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="22" target="2HlbSkl1Hx2XcQlONuJN-122" edge="1">
           <mxGeometry width="100" height="100" relative="1" as="geometry">
             <Array as="points">
-              <mxPoint x="1590" y="469" />
-              <mxPoint x="1590" y="403" />
+              <mxPoint x="1580" y="469" />
+              <mxPoint x="1580" y="340" />
             </Array>
           </mxGeometry>
         </mxCell>
-        <mxCell id="26" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=171;edgeStyle=orthogonalEdgeStyle;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="13" target="104" edge="1">
+        <mxCell id="26" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=171;edgeStyle=orthogonalEdgeStyle;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="13" target="2HlbSkl1Hx2XcQlONuJN-122" edge="1">
           <mxGeometry width="100" height="100" relative="1" as="geometry">
             <Array as="points">
-              <mxPoint x="1590" y="182" />
-              <mxPoint x="1590" y="403" />
+              <mxPoint x="1580" y="182" />
+              <mxPoint x="1580" y="340" />
             </Array>
             <mxPoint x="1623.1999999999998" y="182.2862958122704" as="sourcePoint" />
           </mxGeometry>
@@ -228,12 +228,12 @@
           </mxGeometry>
         </mxCell>
         <mxCell id="57" value="&lt;div style=&#39;width: 93.0px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Start Next Step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ffffff;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=2.0;spacingRight=0;whiteSpace=wrap;gliffyId=311;" parent="1" vertex="1">
-          <mxGeometry x="1479.997548828125" y="531" width="100" height="37.3939208984375" as="geometry" />
+          <mxGeometry x="1979.997548828125" y="531" width="100" height="37.3939208984375" as="geometry" />
         </mxCell>
         <mxCell id="58" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=313;exitX=1.0;exitY=0.5;exitPerimeter=0;entryX=0.0;entryY=0.5;entryPerimeter=0;" parent="1" source="104" target="14" edge="1">
           <mxGeometry width="100" height="100" relative="1" as="geometry">
             <Array as="points">
-              <mxPoint x="1654.002431640625" y="403.5" />
+              <mxPoint x="2147.252431640625" y="340.8" />
             </Array>
           </mxGeometry>
         </mxCell>
@@ -400,11 +400,10 @@
             </Array>
           </mxGeometry>
         </mxCell>
-        <mxCell id="97" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=542;exitX=1.0;exitY=0.5;exitPerimeter=0;entryX=0.0;entryY=0.5;entryPerimeter=0;" parent="1" source="95" target="104" edge="1">
+        <mxCell id="97" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=542;exitX=1.0;exitY=0.5;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="95" target="2HlbSkl1Hx2XcQlONuJN-122" edge="1">
           <mxGeometry width="100" height="100" relative="1" as="geometry">
             <Array as="points">
               <mxPoint x="1590" y="340" />
-              <mxPoint x="1590" y="404" />
             </Array>
           </mxGeometry>
         </mxCell>
@@ -412,21 +411,18 @@
           <mxGeometry x="1226.9024658203125" y="218.75" width="17" height="17" as="geometry" />
         </mxCell>
         <mxCell id="104" style="shape=rhombus;perimeter=rhombusPerimeter;shadow=0;strokeWidth=2;fillColor=#FFFFFF;strokeColor=#333333;opacity=100.0;gliffyId=566;" parent="1" vertex="1">
-          <mxGeometry x="1620.0025537109375" y="389" width="34" height="29" as="geometry" />
+          <mxGeometry x="2091.0025537109377" y="326.3" width="34" height="29" as="geometry" />
         </mxCell>
-        <mxCell id="105" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=568;edgeStyle=orthogonalEdgeStyle;" parent="1" source="104" target="57" edge="1">
+        <mxCell id="105" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=568;edgeStyle=orthogonalEdgeStyle;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="104" target="57" edge="1">
           <mxGeometry width="100" height="100" relative="1" as="geometry">
-            <Array as="points">
-              <mxPoint x="1637" y="551" />
-            </Array>
-            <mxPoint x="1619.997548828125" y="550.7" as="targetPoint" />
+            <mxPoint x="2089.997548828125" y="488.3" as="targetPoint" />
           </mxGeometry>
         </mxCell>
         <mxCell id="106" value="&lt;div style=&#39;width: 64.0px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;left&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Not &lt;/span&gt;&lt;/div&gt;&lt;div align=&quot;left&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Last  &lt;/span&gt;&lt;/div&gt;&lt;div align=&quot;left&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="text;html=1;nl2Br=0;html=1;nl2Br=0;verticalAlign=middle;align=left;spacingLeft=0.0;spacingRight=0;whiteSpace=wrap;gliffyId=570;" parent="1" vertex="1">
-          <mxGeometry x="1641.5025537109375" y="418" width="67" height="42" as="geometry" />
+          <mxGeometry x="2117.75" y="355.3" width="35.25" height="42" as="geometry" />
         </mxCell>
         <mxCell id="107" value="&lt;div style=&#39;width: 68.5px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;left&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Last Step&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="text;html=1;nl2Br=0;html=1;nl2Br=0;verticalAlign=middle;align=left;spacingLeft=0.0;spacingRight=0;whiteSpace=wrap;gliffyId=571;" parent="1" vertex="1">
-          <mxGeometry x="1652.0025537109375" y="383.80303955078125" width="71.5" height="14" as="geometry" />
+          <mxGeometry x="2125.0025537109377" y="319.00303955078124" width="71.5" height="14" as="geometry" />
         </mxCell>
         <mxCell id="110" style="shape=filledEdge;strokeWidth=2;strokeColor=#000000;fillColor=none;startArrow=none;startFill=0;startSize=6;endArrow=block;endFill=1;endSize=6;rounded=0;gliffyId=581;exitX=1.0;exitY=0.5;exitPerimeter=0;entryX=0.0;entryY=0.5;entryPerimeter=0;" parent="1" source="5" target="112" edge="1">
           <mxGeometry width="100" height="100" relative="1" as="geometry">
@@ -489,6 +485,66 @@
         <mxCell id="122" value="&lt;div style=&#39;width: 67.0px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;left&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;Starts the final job&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="text;html=1;nl2Br=0;html=1;nl2Br=0;verticalAlign=middle;align=left;spacingLeft=0.0;spacingRight=0;whiteSpace=wrap;gliffyId=619;" parent="1" vertex="1">
           <mxGeometry x="427" y="381.94696044921875" width="70" height="28" as="geometry" />
         </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-122" value="&lt;div style=&quot;width: 93.0px;height:auto;word-break: break-word;&quot;&gt;&lt;div align=&quot;center&quot;&gt;&lt;font face=&quot;Arial&quot; color=&quot;#000000&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;Soft Body Prepare&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ffffff;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=2.0;spacingRight=0;whiteSpace=wrap;gliffyId=48;" parent="1" vertex="1">
+          <mxGeometry x="1600" y="310.4" width="100" height="59.6" as="geometry" />
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-123" value="&lt;div style=&quot;width: 93.0px;height:auto;word-break: break-word;&quot;&gt;&lt;div&gt;&lt;font face=&quot;Arial&quot; color=&quot;#000000&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;Soft Body &lt;/span&gt;&lt;/font&gt;&lt;span style=&quot;white-space-collapse: preserve; color: rgb(0, 0, 0); font-family: Arial; background-color: initial;&quot;&gt;Collide&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#FFF2CC;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=2.0;spacingRight=0;whiteSpace=wrap;gliffyId=48;" parent="1" vertex="1">
+          <mxGeometry x="1720" y="310.4" width="110" height="59.6" as="geometry" />
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-124" value="&lt;div style=&quot;width: 93.0px;height:auto;word-break: break-word;&quot;&gt;&lt;div align=&quot;center&quot;&gt;&lt;font face=&quot;Arial&quot; color=&quot;#000000&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;Soft Body Simulate&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#FFF2CC;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=2.0;spacingRight=0;whiteSpace=wrap;gliffyId=48;" parent="1" vertex="1">
+          <mxGeometry x="1850" y="310.4" width="100" height="59.6" as="geometry" />
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-125" value="&lt;div style=&quot;width: 93.0px;height:auto;word-break: break-word;&quot;&gt;&lt;div align=&quot;center&quot;&gt;&lt;font face=&quot;Arial&quot; color=&quot;#000000&quot;&gt;&lt;span style=&quot;white-space-collapse: preserve;&quot;&gt;Soft Body Finalize&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ffffff;strokeColor=#333333;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=2.0;spacingRight=0;whiteSpace=wrap;gliffyId=48;" parent="1" vertex="1">
+          <mxGeometry x="1970" y="310.4" width="100" height="59.6" as="geometry" />
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-127" value="" style="endArrow=block;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;strokeColor=#000000;endFill=1;" parent="1" source="2HlbSkl1Hx2XcQlONuJN-122" target="2HlbSkl1Hx2XcQlONuJN-123" edge="1">
+          <mxGeometry width="50" height="50" relative="1" as="geometry">
+            <mxPoint x="2230" y="277.50000000000006" as="sourcePoint" />
+            <mxPoint x="2252.25" y="277.50000000000006" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-128" value="" style="endArrow=block;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;strokeColor=#000000;endFill=1;" parent="1" source="2HlbSkl1Hx2XcQlONuJN-123" target="2HlbSkl1Hx2XcQlONuJN-124" edge="1">
+          <mxGeometry width="50" height="50" relative="1" as="geometry">
+            <mxPoint x="1708" y="350" as="sourcePoint" />
+            <mxPoint x="1730" y="350" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-129" value="" style="endArrow=block;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;strokeColor=#000000;endFill=1;" parent="1" source="2HlbSkl1Hx2XcQlONuJN-124" target="2HlbSkl1Hx2XcQlONuJN-125" edge="1">
+          <mxGeometry width="50" height="50" relative="1" as="geometry">
+            <mxPoint x="1718" y="360" as="sourcePoint" />
+            <mxPoint x="1740" y="360" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-130" value="" style="endArrow=block;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;strokeWidth=2;strokeColor=#000000;endFill=1;" parent="1" source="2HlbSkl1Hx2XcQlONuJN-125" target="104" edge="1">
+          <mxGeometry width="50" height="50" relative="1" as="geometry">
+            <mxPoint x="1728" y="370" as="sourcePoint" />
+            <mxPoint x="1750" y="370" as="targetPoint" />
+          </mxGeometry>
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-134" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;P&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#00ff00;strokeColor=#333333;gradientColor=#AAFFAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=191;" parent="1" vertex="1">
+          <mxGeometry x="1600" y="371.1" width="17" height="17" as="geometry" />
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-136" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;P&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff0000;strokeColor=#333333;gradientColor=#FFAAAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=200;" parent="1" vertex="1">
+          <mxGeometry x="1970" y="370" width="17" height="17" as="geometry" />
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-137" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;V&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff0000;strokeColor=#333333;gradientColor=#FFAAAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=198;" parent="1" vertex="1">
+          <mxGeometry x="1987" y="370" width="17" height="17" as="geometry" />
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-138" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;P&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#00ff00;strokeColor=#333333;gradientColor=#AAFFAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=191;" parent="1" vertex="1">
+          <mxGeometry x="1720" y="370" width="17" height="17" as="geometry" />
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-139" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;V&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#00ff00;strokeColor=#333333;gradientColor=#AAFFAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=194;" parent="1" vertex="1">
+          <mxGeometry x="1737.6599999999999" y="370" width="17" height="17" as="geometry" />
+        </mxCell>
+        <mxCell id="2HlbSkl1Hx2XcQlONuJN-140" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;V&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff0000;strokeColor=#333333;gradientColor=#FFAAAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=198;" parent="1" vertex="1">
+          <mxGeometry x="1850" y="371.1" width="17" height="17" as="geometry" />
+        </mxCell>
+        <mxCell id="1HMQW9uxuVFfJUHc01B5-122" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;A&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff9900;strokeColor=#333333;gradientColor=#FFFFAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=438;" vertex="1" parent="1">
+          <mxGeometry x="2004.0040594482423" y="370" width="17" height="17" as="geometry" />
+        </mxCell>
+        <mxCell id="1HMQW9uxuVFfJUHc01B5-123" value="&lt;div style=&#39;width: 13.32px;height:auto;word-break: break-word;&#39;&gt;&lt;div align=&quot;center&quot;&gt;&lt;span style=&quot;font-size: 12px; font-family: Arial; white-space: pre-wrap; text-decoration: none; line-height: 14px; color: rgb(0, 0, 0);&quot;&gt;A&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;" style="shape=rect;shadow=0;strokeWidth=2;fillColor=#ff0000;strokeColor=#333333;gradientColor=#FFAAAA;gradientDirection=north;opacity=100.0;html=1;nl2Br=0;verticalAlign=middle;align=center;spacingLeft=0.34;spacingRight=0;whiteSpace=wrap;gliffyId=341;" vertex="1" parent="1">
+          <mxGeometry x="2021.0040594482423" y="370" width="17" height="17" as="geometry" />
+        </mxCell>
       </root>
     </mxGraphModel>
   </diagram>

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 0
Docs/PhysicsSystemUpdate.svg


+ 12 - 12
Jolt/Physics/Collision/Shape/BoxShape.cpp

@@ -224,16 +224,16 @@ void BoxShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShape
 		ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
 }
 
-void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
 	Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();
 	Vec3 half_extent = inScale.Abs() * mHalfExtent;
 
-	for (SoftBodyVertex &v : ioVertices)
-		if (v.mInvMass > 0.0f)
+	for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
+		if (v->mInvMass > 0.0f)
 		{
 			// Convert to local space
-			Vec3 local_pos = inverse_transform * v.mPosition;
+			Vec3 local_pos = inverse_transform * v->mPosition;
 
 			// Clamp point to inside box
 			Vec3 clamped_point = Vec3::sMax(Vec3::sMin(local_pos, half_extent), -half_extent);
@@ -245,9 +245,9 @@ void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg
 				Vec3 delta = half_extent - local_pos.Abs();
 				int index = delta.GetLowestComponentIndex();
 				float penetration = delta[index];
-				if (penetration > v.mLargestPenetration)
+				if (penetration > v->mLargestPenetration)
 				{
-					v.mLargestPenetration = penetration;
+					v->mLargestPenetration = penetration;
 
 					// Calculate contact point and normal
 					Vec3 possible_normals[] = { Vec3::sAxisX(), Vec3::sAxisY(), Vec3::sAxisZ() };
@@ -255,8 +255,8 @@ void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg
 					Vec3 point = normal * half_extent;
 
 					// Store collision
-					v.mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
-					v.mCollidingShapeIndex = inCollidingShapeIndex;
+					v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
+					v->mCollidingShapeIndex = inCollidingShapeIndex;
 				}
 			}
 			else
@@ -267,15 +267,15 @@ void BoxShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg
 
 				// Penetration will be negative since we're not penetrating
 				float penetration = -normal_length;
-				if (penetration > v.mLargestPenetration)
+				if (penetration > v->mLargestPenetration)
 				{
 					normal /= normal_length;
 
-					v.mLargestPenetration = penetration;
+					v->mLargestPenetration = penetration;
 
 					// Store collision
-					v.mCollisionPlane = Plane::sFromPointAndNormal(clamped_point, normal).GetTransformed(inCenterOfMassTransform);
-					v.mCollidingShapeIndex = inCollidingShapeIndex;
+					v->mCollisionPlane = Plane::sFromPointAndNormal(clamped_point, normal).GetTransformed(inCenterOfMassTransform);
+					v->mCollidingShapeIndex = inCollidingShapeIndex;
 				}
 			}
 		}

+ 1 - 1
Jolt/Physics/Collision/Shape/BoxShape.h

@@ -77,7 +77,7 @@ public:
 	virtual void			CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::GetTrianglesStart
 	virtual void			GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;

+ 12 - 12
Jolt/Physics/Collision/Shape/CapsuleShape.cpp

@@ -322,7 +322,7 @@ void CapsuleShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubS
 		ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
 }
 
-void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
 	JPH_ASSERT(IsValidScale(inScale));
 
@@ -333,11 +333,11 @@ void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec
 	float half_height_of_cylinder = scale * mHalfHeightOfCylinder;
 	float radius = scale * mRadius;
 
-	for (SoftBodyVertex &v : ioVertices)
-		if (v.mInvMass > 0.0f)
+	for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
+		if (v->mInvMass > 0.0f)
 		{
 			// Calculate penetration
-			Vec3 local_pos = inverse_transform * v.mPosition;
+			Vec3 local_pos = inverse_transform * v->mPosition;
 			if (abs(local_pos.GetY()) <= half_height_of_cylinder)
 			{
 				// Near cylinder
@@ -345,17 +345,17 @@ void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec
 				normal.SetY(0.0f);
 				float normal_length = normal.Length();
 				float penetration = radius - normal_length;
-				if (penetration > v.mLargestPenetration)
+				if (penetration > v->mLargestPenetration)
 				{
-					v.mLargestPenetration = penetration;
+					v->mLargestPenetration = penetration;
 
 					// Calculate contact point and normal
 					normal = normal_length > 0.0f? normal / normal_length : Vec3::sAxisX();
 					Vec3 point = radius * normal;
 
 					// Store collision
-					v.mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
-					v.mCollidingShapeIndex = inCollidingShapeIndex;
+					v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
+					v->mCollidingShapeIndex = inCollidingShapeIndex;
 				}
 			}
 			else
@@ -365,17 +365,17 @@ void CapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec
 				Vec3 delta = local_pos - center;
 				float distance = delta.Length();
 				float penetration = radius - distance;
-				if (penetration > v.mLargestPenetration)
+				if (penetration > v->mLargestPenetration)
 				{
-					v.mLargestPenetration = penetration;
+					v->mLargestPenetration = penetration;
 
 					// Calculate contact point and normal
 					Vec3 normal = delta / distance;
 					Vec3 point = center + radius * normal;
 
 					// Store collision
-					v.mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
-					v.mCollidingShapeIndex = inCollidingShapeIndex;
+					v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal).GetTransformed(inCenterOfMassTransform);
+					v->mCollidingShapeIndex = inCollidingShapeIndex;
 				}
 			}
 		}

+ 1 - 1
Jolt/Physics/Collision/Shape/CapsuleShape.h

@@ -86,7 +86,7 @@ public:
 	virtual void			CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::TransformShape
 	virtual void			TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;

+ 2 - 2
Jolt/Physics/Collision/Shape/CompoundShape.cpp

@@ -246,10 +246,10 @@ void CompoundShape::DrawGetSupportingFace(DebugRenderer *inRenderer, RMat44Arg i
 }
 #endif // JPH_DEBUG_RENDERER
 
-void CompoundShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void CompoundShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
 	for (const SubShape &shape : mSubShapes)
-		shape.mShape->CollideSoftBodyVertices(inCenterOfMassTransform * Mat44::sRotationTranslation(shape.GetRotation(), shape.GetPositionCOM()), shape.TransformScale(inScale), ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
+		shape.mShape->CollideSoftBodyVertices(inCenterOfMassTransform * Mat44::sRotationTranslation(shape.GetRotation(), shape.GetPositionCOM()), shape.TransformScale(inScale), ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
 }
 
 void CompoundShape::TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const

+ 1 - 1
Jolt/Physics/Collision/Shape/CompoundShape.h

@@ -106,7 +106,7 @@ public:
 #endif // JPH_DEBUG_RENDERER
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::TransformShape
 	virtual void					TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;

+ 8 - 8
Jolt/Physics/Collision/Shape/ConvexHullShape.cpp

@@ -1058,7 +1058,7 @@ void ConvexHullShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inS
 	ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
 }
 
-void ConvexHullShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void ConvexHullShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
 	Mat44 inverse_transform = inCenterOfMassTransform.InversedRotationTranslation();
 
@@ -1066,10 +1066,10 @@ void ConvexHullShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform,
 	bool is_not_scaled = ScaleHelpers::IsNotScaled(inScale);
 	float scale_flip = ScaleHelpers::IsInsideOut(inScale)? -1.0f : 1.0f;
 
-	for (SoftBodyVertex &v : ioVertices)
-		if (v.mInvMass > 0.0f)
+	for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
+		if (v->mInvMass > 0.0f)
 		{
-			Vec3 local_pos = inverse_transform * v.mPosition;
+			Vec3 local_pos = inverse_transform * v->mPosition;
 
 			// Find most facing plane
 			float max_distance = -FLT_MAX;
@@ -1151,17 +1151,17 @@ void ConvexHullShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform,
 				penetration = -penetration;
 			else
 				normal = -normal;
-			if (penetration > v.mLargestPenetration)
+			if (penetration > v->mLargestPenetration)
 			{
-				v.mLargestPenetration = penetration;
+				v->mLargestPenetration = penetration;
 
 				// Calculate contact plane
 				normal = normal_length > 0.0f? normal / normal_length : max_plane_normal;
 				Plane plane = Plane::sFromPointAndNormal(closest_point, normal);
 
 				// Store collision
-				v.mCollisionPlane = plane.GetTransformed(inCenterOfMassTransform);
-				v.mCollidingShapeIndex = inCollidingShapeIndex;
+				v->mCollisionPlane = plane.GetTransformed(inCenterOfMassTransform);
+				v->mCollidingShapeIndex = inCollidingShapeIndex;
 			}
 		}
 }

+ 1 - 1
Jolt/Physics/Collision/Shape/ConvexHullShape.h

@@ -90,7 +90,7 @@ public:
 	virtual void			CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::GetTrianglesStart
 	virtual void			GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;

+ 8 - 8
Jolt/Physics/Collision/Shape/CylinderShape.cpp

@@ -300,7 +300,7 @@ void CylinderShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSub
 		ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
 }
 
-void CylinderShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void CylinderShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
 	JPH_ASSERT(IsValidScale(inScale));
 
@@ -311,10 +311,10 @@ void CylinderShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Ve
 	float half_height = abs_scale.GetY() * mHalfHeight;
 	float radius = abs_scale.GetX() * mRadius;
 
-	for (SoftBodyVertex &v : ioVertices)
-		if (v.mInvMass > 0.0f)
+	for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
+		if (v->mInvMass > 0.0f)
 		{
-			Vec3 local_pos = inverse_transform * v.mPosition;
+			Vec3 local_pos = inverse_transform * v->mPosition;
 
 			// Calculate penetration into side surface
 			Vec3 side_normal = local_pos;
@@ -348,13 +348,13 @@ void CylinderShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Ve
 			// Calculate penetration
 			Plane plane = Plane::sFromPointAndNormal(point, normal);
 			float penetration = -plane.SignedDistance(local_pos);
-			if (penetration > v.mLargestPenetration)
+			if (penetration > v->mLargestPenetration)
 			{
-				v.mLargestPenetration = penetration;
+				v->mLargestPenetration = penetration;
 
 				// Store collision
-				v.mCollisionPlane = plane.GetTransformed(inCenterOfMassTransform);
-				v.mCollidingShapeIndex = inCollidingShapeIndex;
+				v->mCollisionPlane = plane.GetTransformed(inCenterOfMassTransform);
+				v->mCollidingShapeIndex = inCollidingShapeIndex;
 			}
 		}
 }

+ 1 - 1
Jolt/Physics/Collision/Shape/CylinderShape.h

@@ -81,7 +81,7 @@ public:
 	virtual void			CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::TransformShape
 	virtual void			TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;

+ 2 - 2
Jolt/Physics/Collision/Shape/HeightFieldShape.cpp

@@ -1555,9 +1555,9 @@ void HeightFieldShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &in
 	// A height field doesn't have volume, so we can't test insideness
 }
 
-void HeightFieldShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void HeightFieldShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
-	sCollideSoftBodyVerticesUsingRayCast(*this, inCenterOfMassTransform, inScale, ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
+	sCollideSoftBodyVerticesUsingRayCast(*this, inCenterOfMassTransform, inScale, ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
 }
 
 void HeightFieldShape::sCastConvexVsHeightField(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)

+ 1 - 1
Jolt/Physics/Collision/Shape/HeightFieldShape.h

@@ -146,7 +146,7 @@ public:
 	virtual void					CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::GetTrianglesStart
 	virtual void					GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;

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

@@ -779,9 +779,9 @@ void MeshShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShap
 	sCollidePointUsingRayCast(*this, inPoint, inSubShapeIDCreator, ioCollector, inShapeFilter);
 }
 
-void MeshShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void MeshShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
-	sCollideSoftBodyVerticesUsingRayCast(*this, inCenterOfMassTransform, inScale, ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
+	sCollideSoftBodyVerticesUsingRayCast(*this, inCenterOfMassTransform, inScale, ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
 }
 
 void MeshShape::sCastConvexVsMesh(const ShapeCast &inShapeCast, const ShapeCastSettings &inShapeCastSettings, const Shape *inShape, Vec3Arg inScale, [[maybe_unused]] const ShapeFilter &inShapeFilter, Mat44Arg inCenterOfMassTransform2, const SubShapeIDCreator &inSubShapeIDCreator1, const SubShapeIDCreator &inSubShapeIDCreator2, CastShapeCollector &ioCollector)

+ 1 - 1
Jolt/Physics/Collision/Shape/MeshShape.h

@@ -118,7 +118,7 @@ public:
 	virtual void					CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::GetTrianglesStart
 	virtual void					GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;

+ 2 - 2
Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.cpp

@@ -127,9 +127,9 @@ void OffsetCenterOfMassShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCrea
 	mInnerShape->CollidePoint(inPoint + mOffset, inSubShapeIDCreator, ioCollector, inShapeFilter);
 }
 
-void OffsetCenterOfMassShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void OffsetCenterOfMassShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
-	mInnerShape->CollideSoftBodyVertices(inCenterOfMassTransform.PreTranslated(-inScale * mOffset), inScale, ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
+	mInnerShape->CollideSoftBodyVertices(inCenterOfMassTransform.PreTranslated(-inScale * mOffset), inScale, ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
 }
 
 void OffsetCenterOfMassShape::CollectTransformedShapes(const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale, const SubShapeIDCreator &inSubShapeIDCreator, TransformedShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) const

+ 1 - 1
Jolt/Physics/Collision/Shape/OffsetCenterOfMassShape.h

@@ -97,7 +97,7 @@ public:
 	virtual void					CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::CollectTransformedShapes
 	virtual void					CollectTransformedShapes(const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale, const SubShapeIDCreator &inSubShapeIDCreator, TransformedShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) const override;

+ 2 - 2
Jolt/Physics/Collision/Shape/RotatedTranslatedShape.cpp

@@ -161,9 +161,9 @@ void RotatedTranslatedShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreat
 	mInnerShape->CollidePoint(transform * inPoint, inSubShapeIDCreator, ioCollector, inShapeFilter);
 }
 
-void RotatedTranslatedShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void RotatedTranslatedShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
-	mInnerShape->CollideSoftBodyVertices(inCenterOfMassTransform * Mat44::sRotation(mRotation), inScale, ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
+	mInnerShape->CollideSoftBodyVertices(inCenterOfMassTransform * Mat44::sRotation(mRotation), inScale, ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
 }
 
 void RotatedTranslatedShape::CollectTransformedShapes(const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale, const SubShapeIDCreator &inSubShapeIDCreator, TransformedShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) const

+ 1 - 1
Jolt/Physics/Collision/Shape/RotatedTranslatedShape.h

@@ -98,7 +98,7 @@ public:
 	virtual void					CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::CollectTransformedShapes
 	virtual void					CollectTransformedShapes(const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale, const SubShapeIDCreator &inSubShapeIDCreator, TransformedShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) const override;

+ 2 - 2
Jolt/Physics/Collision/Shape/ScaledShape.cpp

@@ -132,9 +132,9 @@ void ScaledShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubSh
 	mInnerShape->CollidePoint(inv_scale * inPoint, inSubShapeIDCreator, ioCollector, inShapeFilter);
 }
 
-void ScaledShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void ScaledShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
-	mInnerShape->CollideSoftBodyVertices(inCenterOfMassTransform, inScale * mScale, ioVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
+	mInnerShape->CollideSoftBodyVertices(inCenterOfMassTransform, inScale * mScale, ioVertices, inNumVertices, inDeltaTime, inDisplacementDueToGravity, inCollidingShapeIndex);
 }
 
 void ScaledShape::CollectTransformedShapes(const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale, const SubShapeIDCreator &inSubShapeIDCreator, TransformedShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) const

+ 1 - 1
Jolt/Physics/Collision/Shape/ScaledShape.h

@@ -94,7 +94,7 @@ public:
 	virtual void					CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::CollectTransformedShapes
 	virtual void					CollectTransformedShapes(const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale, const SubShapeIDCreator &inSubShapeIDCreator, TransformedShapeCollector &ioCollector, const ShapeFilter &inShapeFilter) const override;

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

@@ -271,37 +271,37 @@ Shape::ShapeResult Shape::ScaleShape(Vec3Arg inScale) const
 	return compound.Create();
 }
 
-void Shape::sCollideSoftBodyVerticesUsingRayCast(const Shape &inShape, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex)
+void Shape::sCollideSoftBodyVerticesUsingRayCast(const Shape &inShape, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex)
 {
 	Mat44 inverse_transform = Mat44::sScale(inScale.Reciprocal()) * inCenterOfMassTransform.InversedRotationTranslation();
 	Mat44 direction_preserving_transform = inverse_transform.Transposed3x3(); // To transform normals: transpose of the inverse
 
-	for (SoftBodyVertex &v : ioVertices)
-		if (v.mInvMass > 0.0f)
+	for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
+		if (v->mInvMass > 0.0f)
 		{
 			// Calculate the distance we will move this frame
-			Vec3 movement = v.mVelocity * inDeltaTime + inDisplacementDueToGravity;
+			Vec3 movement = v->mVelocity * inDeltaTime + inDisplacementDueToGravity;
 
 			RayCastResult hit;
 			hit.mFraction = 2.0f; // Add a little extra distance in case the particle speeds up
 
-			RayCast ray(v.mPosition - 0.5f * movement, movement); // Start a little early in case we penetrated before
+			RayCast ray(v->mPosition - 0.5f * movement, movement); // Start a little early in case we penetrated before
 
 			if (inShape.CastRay(ray.Transformed(inverse_transform), SubShapeIDCreator(), hit))
 			{
 				// Calculate penetration
 				float penetration = (0.5f - hit.mFraction) * movement.Length();
-				if (penetration > v.mLargestPenetration)
+				if (penetration > v->mLargestPenetration)
 				{
-					v.mLargestPenetration = penetration;
+					v->mLargestPenetration = penetration;
 
 					// Calculate contact point and normal
 					Vec3 point = ray.GetPointOnRay(hit.mFraction);
 					Vec3 normal = direction_preserving_transform.Multiply3x3(inShape.GetSurfaceNormal(hit.mSubShapeID2, inverse_transform * point)).Normalized();
 
 					// Store collision
-					v.mCollisionPlane = Plane::sFromPointAndNormal(point, normal);
-					v.mCollidingShapeIndex = inCollidingShapeIndex;
+					v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal);
+					v->mCollidingShapeIndex = inCollidingShapeIndex;
 				}
 			}
 		}

+ 3 - 2
Jolt/Physics/Collision/Shape/Shape.h

@@ -306,10 +306,11 @@ public:
 	/// @param inCenterOfMassTransform Center of mass transform for this shape relative to the vertices.
 	/// @param inScale The scale to use for this shape
 	/// @param ioVertices The vertices of the soft body
+	/// @param inNumVertices The number of vertices in ioVertices
 	/// @param inDeltaTime Delta time of this time step (can be used to extrapolate the position using the velocity of the particle)
 	/// @param inDisplacementDueToGravity Displacement due to gravity during this time step
 	/// @param inCollidingShapeIndex Value to store in SoftBodyVertex::mCollidingShapeIndex when a collision was found
-	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const = 0;
+	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const = 0;
 
 	/// Collect the leaf transformed shapes of all leaf shapes of this shape.
 	/// inBox is the world space axis aligned box which leaf shapes should collide with.
@@ -423,7 +424,7 @@ protected:
 	static void						sCollidePointUsingRayCast(const Shape &inShape, Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter);
 
 	/// A fallback version of CollideSoftBodyVertices that uses a raycast to collide the vertices with the shape.
-	static void						sCollideSoftBodyVerticesUsingRayCast(const Shape &inShape, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex);
+	static void						sCollideSoftBodyVerticesUsingRayCast(const Shape &inShape, Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex);
 
 private:
 	uint64							mUserData = 0;

+ 8 - 8
Jolt/Physics/Collision/Shape/SphereShape.cpp

@@ -275,29 +275,29 @@ void SphereShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubSh
 		ioCollector.AddHit({ TransformedShape::sGetBodyID(ioCollector.GetContext()), inSubShapeIDCreator.GetID() });
 }
 
-void SphereShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void SphereShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
 	Vec3 center = inCenterOfMassTransform.GetTranslation();
 	float radius = GetScaledRadius(inScale);
 
-	for (SoftBodyVertex &v : ioVertices)
-		if (v.mInvMass > 0.0f)
+	for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
+		if (v->mInvMass > 0.0f)
 		{
 			// Calculate penetration
-			Vec3 delta = v.mPosition - center;
+			Vec3 delta = v->mPosition - center;
 			float distance = delta.Length();
 			float penetration = radius - distance;
-			if (penetration > v.mLargestPenetration)
+			if (penetration > v->mLargestPenetration)
 			{
-				v.mLargestPenetration = penetration;
+				v->mLargestPenetration = penetration;
 
 				// Calculate contact point and normal
 				Vec3 normal = distance > 0.0f? delta / distance : Vec3::sAxisY();
 				Vec3 point = center + radius * normal;
 
 				// Store collision
-				v.mCollisionPlane = Plane::sFromPointAndNormal(point, normal);
-				v.mCollidingShapeIndex = inCollidingShapeIndex;
+				v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal);
+				v->mCollidingShapeIndex = inCollidingShapeIndex;
 			}
 		}
 }

+ 1 - 1
Jolt/Physics/Collision/Shape/SphereShape.h

@@ -81,7 +81,7 @@ public:
 	virtual void			CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::TransformShape
 	virtual void			TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;

+ 8 - 8
Jolt/Physics/Collision/Shape/TaperedCapsuleShape.cpp

@@ -300,7 +300,7 @@ AABox TaperedCapsuleShape::GetWorldSpaceBounds(Mat44Arg inCenterOfMassTransform,
 	return AABox(p1, p2);
 }
 
-void TaperedCapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void TaperedCapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, [[maybe_unused]] float inDeltaTime, [[maybe_unused]] Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
 	JPH_ASSERT(IsValidScale(inScale));
 
@@ -316,10 +316,10 @@ void TaperedCapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransfo
 	float scaled_top_radius = scale_xz * mTopRadius;
 	float scaled_bottom_radius = scale_xz * mBottomRadius;
 
-	for (SoftBodyVertex &v : ioVertices)
-		if (v.mInvMass > 0.0f)
+	for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
+		if (v->mInvMass > 0.0f)
 		{
-			Vec3 local_pos = scale_y_flip * (inverse_transform * v.mPosition);
+			Vec3 local_pos = scale_y_flip * (inverse_transform * v->mPosition);
 
 			// See comments at TaperedCapsuleShape::GetSurfaceNormal for rationale behind the math
 			Vec3 position, normal;
@@ -348,16 +348,16 @@ void TaperedCapsuleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransfo
 
 			Plane plane = Plane::sFromPointAndNormal(position, normal);
 			float penetration = -plane.SignedDistance(local_pos);
-			if (penetration > v.mLargestPenetration)
+			if (penetration > v->mLargestPenetration)
 			{
-				v.mLargestPenetration = penetration;
+				v->mLargestPenetration = penetration;
 
 				// Need to flip the normal's y if capsule is flipped (this corresponds to flipping both the point and the normal around y)
 				plane.SetNormal(scale_y_flip * plane.GetNormal());
 
 				// Store collision
-				v.mCollisionPlane = plane.GetTransformed(inCenterOfMassTransform);
-				v.mCollidingShapeIndex = inCollidingShapeIndex;
+				v->mCollisionPlane = plane.GetTransformed(inCenterOfMassTransform);
+				v->mCollidingShapeIndex = inCollidingShapeIndex;
 			}
 		}
 }

+ 1 - 1
Jolt/Physics/Collision/Shape/TaperedCapsuleShape.h

@@ -72,7 +72,7 @@ public:
 	virtual const Support *	GetSupportFunction(ESupportMode inMode, SupportBuffer &inBuffer, Vec3Arg inScale) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 #ifdef JPH_DEBUG_RENDERER
 	// See Shape::Draw

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

@@ -259,7 +259,7 @@ void TriangleShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSub
 	// Can't be inside a triangle
 }
 
-void TriangleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void TriangleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
 	Vec3 v1 = inCenterOfMassTransform * (inScale * mV1);
 	Vec3 v2 = inCenterOfMassTransform * (inScale * mV2);
@@ -270,26 +270,26 @@ void TriangleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Ve
 
 	Vec3 triangle_normal = (v2 - v1).Cross(v3 - v1).NormalizedOr(Vec3::sAxisY());
 
-	for (SoftBodyVertex &v : ioVertices)
-		if (v.mInvMass > 0.0f)
+	for (SoftBodyVertex *v = ioVertices, *sbv_end = ioVertices + inNumVertices; v < sbv_end; ++v)
+		if (v->mInvMass > 0.0f)
 		{
 			// Get the closest point from the vertex to the triangle
 			uint32 set;
-			Vec3 v1_minus_position = v1 - v.mPosition;
-			Vec3 closest_point = ClosestPoint::GetClosestPointOnTriangle(v1_minus_position, v2 - v.mPosition, v3 - v.mPosition, set);
+			Vec3 v1_minus_position = v1 - v->mPosition;
+			Vec3 closest_point = ClosestPoint::GetClosestPointOnTriangle(v1_minus_position, v2 - v->mPosition, v3 - v->mPosition, set);
 
 			if (set == 0b111)
 			{
 				// Closest is interior to the triangle, use plane as collision plane but don't allow more than 10cm penetration
 				// because otherwise a triangle half a level a way will have a huge penetration if it is back facing
 				float penetration = min(triangle_normal.Dot(v1_minus_position), 0.1f);
-				if (penetration > v.mLargestPenetration)
+				if (penetration > v->mLargestPenetration)
 				{
-					v.mLargestPenetration = penetration;
+					v->mLargestPenetration = penetration;
 
 					// Store collision
-					v.mCollisionPlane = Plane::sFromPointAndNormal(v1, triangle_normal);
-					v.mCollidingShapeIndex = inCollidingShapeIndex;
+					v->mCollisionPlane = Plane::sFromPointAndNormal(v1, triangle_normal);
+					v->mCollidingShapeIndex = inCollidingShapeIndex;
 				}
 			}
 			else if (closest_point.Dot(triangle_normal) < 0.0f) // Ignore back facing edges
@@ -297,17 +297,17 @@ void TriangleShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Ve
 				// Closest point is on an edge or vertex, use closest point as collision plane
 				float closest_point_length = closest_point.Length();
 				float penetration = -closest_point_length;
-				if (penetration > v.mLargestPenetration)
+				if (penetration > v->mLargestPenetration)
 				{
-					v.mLargestPenetration = penetration;
+					v->mLargestPenetration = penetration;
 
 					// Calculate contact point and normal
-					Vec3 point = v.mPosition + closest_point;
+					Vec3 point = v->mPosition + closest_point;
 					Vec3 normal = -closest_point / closest_point_length;
 
 					// Store collision
-					v.mCollisionPlane = Plane::sFromPointAndNormal(point, normal);
-					v.mCollidingShapeIndex = inCollidingShapeIndex;
+					v->mCollisionPlane = Plane::sFromPointAndNormal(point, normal);
+					v->mCollidingShapeIndex = inCollidingShapeIndex;
 				}
 			}
 		}

+ 1 - 1
Jolt/Physics/Collision/Shape/TriangleShape.h

@@ -85,7 +85,7 @@ public:
 	virtual void			CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 
 	// See: Shape::ColideSoftBodyVertices
-	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void			CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 
 	// See Shape::TransformShape
 	virtual void			TransformShape(Mat44Arg inCenterOfMassTransform, TransformedShapeCollector &ioCollector) const override;

+ 167 - 28
Jolt/Physics/PhysicsSystem.cpp

@@ -58,7 +58,10 @@ static const Color cColorResolveCCDContacts = Color::sGetDistinctColor(16);
 static const Color cColorSolvePositionConstraints = Color::sGetDistinctColor(17);
 static const Color cColorFindCCDContacts = Color::sGetDistinctColor(18);
 static const Color cColorStepListeners = Color::sGetDistinctColor(19);
-static const Color cColorUpdateSoftBodies = Color::sGetDistinctColor(20);
+static const Color cColorSoftBodyPrepare = Color::sGetDistinctColor(20);
+static const Color cColorSoftBodyCollide = Color::sGetDistinctColor(21);
+static const Color cColorSoftBodySimulate = Color::sGetDistinctColor(22);
+static const Color cColorSoftBodyFinalize = Color::sGetDistinctColor(23);
 
 PhysicsSystem::~PhysicsSystem()
 {
@@ -485,21 +488,18 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 						context.mPhysicsSystem->JobSolvePositionConstraints(&context, &step);
 
 						// Kick the next step
-						if (step.mUpdateSoftBodies.IsValid())
-							step.mUpdateSoftBodies.RemoveDependency();
+						if (step.mSoftBodyPrepare.IsValid())
+							step.mSoftBodyPrepare.RemoveDependency();
 					}, 2); // depends on: resolve ccd contacts, finish building jobs.
 
 			// Unblock previous job.
 			step.mResolveCCDContacts.RemoveDependency();
 
-			step.mUpdateSoftBodies = inJobSystem->CreateJob("UpdateSoftBodies", cColorUpdateSoftBodies, [&context, &step]()
-					{
-						context.mPhysicsSystem->JobUpdateSoftBodies(&context);
-
-						// Kick the next step
-						if (step.mStartNextStep.IsValid())
-							step.mStartNextStep.RemoveDependency();
-					}, max_concurrency); // depends on: solve position constraints.
+			// The soft body prepare job will create other jobs if needed
+			step.mSoftBodyPrepare = inJobSystem->CreateJob("SoftBodyPrepare", cColorSoftBodyPrepare, [&context, &step]()
+				{
+					context.mPhysicsSystem->JobSoftBodyPrepare(&context, &step);
+				}, max_concurrency); // depends on: solve position constraints.
 
 			// Unblock previous jobs
 			JobHandle::sRemoveDependencies(step.mSolvePositionConstraints);
@@ -540,8 +540,8 @@ EPhysicsUpdateError PhysicsSystem::Update(float inDeltaTime, int inCollisionStep
 			for (const JobHandle &h : step.mSolvePositionConstraints)
 				handles.push_back(h);
 			handles.push_back(step.mContactRemovedCallbacks);
-			if (step.mUpdateSoftBodies.IsValid())
-				handles.push_back(step.mUpdateSoftBodies);
+			if (step.mSoftBodyPrepare.IsValid())
+				handles.push_back(step.mSoftBodyPrepare);
 			if (step.mStartNextStep.IsValid())
 				handles.push_back(step.mStartNextStep);
 		}
@@ -2340,12 +2340,152 @@ void PhysicsSystem::JobSolvePositionConstraints(PhysicsUpdateContext *ioContext,
 	while (check_islands || check_split_islands);
 }
 
-void PhysicsSystem::JobUpdateSoftBodies(const PhysicsUpdateContext *ioContext)
+void PhysicsSystem::JobSoftBodyPrepare(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep)
 {
 	JPH_PROFILE_FUNCTION();
 
 #ifdef JPH_ENABLE_ASSERTS
-	// Can activate bodies only
+	// Reading soft body positions
+	BodyAccess::Grant grant(BodyAccess::EAccess::None, BodyAccess::EAccess::Read);
+#endif
+
+	// Get the active soft bodies
+	BodyIDVector active_bodies;
+	mBodyManager.GetActiveBodies(EBodyType::SoftBody, active_bodies);
+
+	// Quit if there are no active soft bodies
+	if (active_bodies.empty())
+	{
+		// Kick the next step
+		if (ioStep->mStartNextStep.IsValid())
+			ioStep->mStartNextStep.RemoveDependency();
+		return;
+	}
+
+	// Sort to get a deterministic update order
+	QuickSort(active_bodies.begin(), active_bodies.end());
+
+	// Allocate soft body contexts
+	ioContext->mNumSoftBodies = (uint)active_bodies.size();
+	ioContext->mSoftBodyUpdateContexts = (SoftBodyUpdateContext *)ioContext->mTempAllocator->Allocate(ioContext->mNumSoftBodies * sizeof(SoftBodyUpdateContext));
+
+	// Initialize soft body contexts
+	for (SoftBodyUpdateContext *sb_ctx = ioContext->mSoftBodyUpdateContexts, *sb_ctx_end = ioContext->mSoftBodyUpdateContexts + ioContext->mNumSoftBodies; sb_ctx < sb_ctx_end; ++sb_ctx)
+	{
+		new (sb_ctx) SoftBodyUpdateContext;
+		Body &body = mBodyManager.GetBody(active_bodies[sb_ctx - ioContext->mSoftBodyUpdateContexts]);
+		SoftBodyMotionProperties *mp = static_cast<SoftBodyMotionProperties *>(body.GetMotionProperties());
+		mp->InitializeUpdateContext(ioContext->mStepDeltaTime, body, *this, *sb_ctx);
+	}
+
+	// We're ready to collide the first soft body
+	ioContext->mSoftBodyToCollide.store(0, memory_order_release);
+
+	// Determine number of jobs to spawn
+	int num_soft_body_jobs = ioContext->GetMaxConcurrency();
+
+	// Create finalize job
+	ioStep->mSoftBodyFinalize = ioContext->mJobSystem->CreateJob("SoftBodyFinalize", cColorSoftBodyFinalize, [ioContext, ioStep]()
+	{
+		ioContext->mPhysicsSystem->JobSoftBodyFinalize(ioContext);
+
+		// Kick the next step
+		if (ioStep->mStartNextStep.IsValid())
+			ioStep->mStartNextStep.RemoveDependency();
+	}, num_soft_body_jobs); // depends on: soft body simulate
+	ioContext->mBarrier->AddJob(ioStep->mSoftBodyFinalize);
+
+	// Create simulate jobs
+	ioStep->mSoftBodySimulate.resize(num_soft_body_jobs);
+	for (int i = 0; i < num_soft_body_jobs; ++i)
+		ioStep->mSoftBodySimulate[i] = ioContext->mJobSystem->CreateJob("SoftBodySimulate", cColorSoftBodySimulate, [ioStep, i]()
+			{
+				ioStep->mContext->mPhysicsSystem->JobSoftBodySimulate(ioStep->mContext, i);
+
+				ioStep->mSoftBodyFinalize.RemoveDependency();
+			}, num_soft_body_jobs); // depends on: soft body collide
+	ioContext->mBarrier->AddJobs(ioStep->mSoftBodySimulate.data(), ioStep->mSoftBodySimulate.size());
+
+	// Create collision jobs
+	ioStep->mSoftBodyCollide.resize(num_soft_body_jobs);
+	for (int i = 0; i < num_soft_body_jobs; ++i)
+		ioStep->mSoftBodyCollide[i] = ioContext->mJobSystem->CreateJob("SoftBodyCollide", cColorSoftBodyCollide, [ioContext, ioStep]()
+			{
+				ioContext->mPhysicsSystem->JobSoftBodyCollide(ioContext);
+
+				for (const JobHandle &h : ioStep->mSoftBodySimulate)
+					h.RemoveDependency();
+			}); // depends on: nothing
+	ioContext->mBarrier->AddJobs(ioStep->mSoftBodyCollide.data(), ioStep->mSoftBodyCollide.size());
+}
+
+void PhysicsSystem::JobSoftBodyCollide(PhysicsUpdateContext *ioContext) const
+{
+#ifdef JPH_ENABLE_ASSERTS
+	// Reading rigid body positions and velocities
+	BodyAccess::Grant grant(BodyAccess::EAccess::Read, BodyAccess::EAccess::Read);
+#endif
+
+	for (;;)
+	{
+		// Fetch the next soft body
+		uint sb_idx = ioContext->mSoftBodyToCollide.fetch_add(1, std::memory_order_acquire);
+		if (sb_idx >= ioContext->mNumSoftBodies)
+			break;
+
+		// Do a broadphase check
+		SoftBodyUpdateContext &sb_ctx = ioContext->mSoftBodyUpdateContexts[sb_idx];
+		sb_ctx.mMotionProperties->DetermineCollidingShapes(sb_ctx, *this);
+	}
+}
+
+void PhysicsSystem::JobSoftBodySimulate(PhysicsUpdateContext *ioContext, uint inThreadIndex) const
+{
+#ifdef JPH_ENABLE_ASSERTS
+	// Updating velocities of soft bodies
+	BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::None);
+#endif
+
+	// Calculate at which body we start to distribute the workload across the threads
+	uint num_soft_bodies = ioContext->mNumSoftBodies;
+	uint start_idx = inThreadIndex * num_soft_bodies / ioContext->GetMaxConcurrency();
+
+	// Keep running partial updates until everything has been updated
+	uint status;
+	do
+	{
+		// Reset status
+		status = 0;
+
+		// Update all soft bodies
+		for (uint i = 0; i < num_soft_bodies; ++i)
+		{
+			// Fetch the soft body context
+			SoftBodyUpdateContext &sb_ctx = ioContext->mSoftBodyUpdateContexts[(start_idx + i) % num_soft_bodies];
+
+			// To avoid trashing the cache too much, we prefer to stick to one soft body until we cannot progress it any further
+			uint sb_status;
+			do
+			{
+				sb_status = (uint)sb_ctx.mMotionProperties->ParallelUpdate(sb_ctx, mPhysicsSettings);
+				status |= sb_status;
+			} while (sb_status == (uint)SoftBodyMotionProperties::EStatus::DidWork);
+		}
+
+		// If we didn't perform any work, yield the thread so that something else can run
+		if (!(status & (uint)SoftBodyMotionProperties::EStatus::DidWork))
+			std::this_thread::yield();
+	}
+	while (status != (uint)SoftBodyMotionProperties::EStatus::Done);
+}
+
+void PhysicsSystem::JobSoftBodyFinalize(PhysicsUpdateContext *ioContext)
+{
+#ifdef JPH_ENABLE_ASSERTS
+	// Updating rigid body velocities and soft body positions / velocities
+	BodyAccess::Grant grant(BodyAccess::EAccess::ReadWrite, BodyAccess::EAccess::ReadWrite);
+
+	// Can activate and deactivate bodies
 	BodyManager::GrantActiveBodiesAccess grant_active(true, true);
 #endif
 
@@ -2355,20 +2495,16 @@ void PhysicsSystem::JobUpdateSoftBodies(const PhysicsUpdateContext *ioContext)
 	BodyID *bodies_to_put_to_sleep = (BodyID *)JPH_STACK_ALLOC(cBodiesBatch * sizeof(BodyID));
 	int num_bodies_to_put_to_sleep = 0;
 
-	// Loop through active bodies
-	BodyIDVector active_bodies;
-	mBodyManager.GetActiveBodies(EBodyType::SoftBody, active_bodies);
-	for (BodyID b : active_bodies)
+	for (SoftBodyUpdateContext *sb_ctx = ioContext->mSoftBodyUpdateContexts, *sb_ctx_end = ioContext->mSoftBodyUpdateContexts + ioContext->mNumSoftBodies; sb_ctx < sb_ctx_end; ++sb_ctx)
 	{
-		Body &body = mBodyManager.GetBody(b);
-		SoftBodyMotionProperties *mp = static_cast<SoftBodyMotionProperties *>(body.GetMotionProperties());
+		// Apply the rigid body velocity deltas
+		sb_ctx->mMotionProperties->UpdateRigidBodyVelocities(*sb_ctx, *this);
 
-		// Update the soft body
-		Vec3 delta_position;
-		ECanSleep can_sleep = mp->Update(ioContext->mStepDeltaTime, body, delta_position, *this);
-		body.SetPositionAndRotationInternal(body.GetPosition() + delta_position, body.GetRotation(), false);
+		// Update the position
+		sb_ctx->mBody->SetPositionAndRotationInternal(sb_ctx->mBody->GetPosition() + sb_ctx->mDeltaPosition, sb_ctx->mBody->GetRotation(), false);
 
-		bodies_to_update_bounds[num_bodies_to_update_bounds++] = b;
+		BodyID id = sb_ctx->mBody->GetID();
+		bodies_to_update_bounds[num_bodies_to_update_bounds++] = id;
 		if (num_bodies_to_update_bounds == cBodiesBatch)
 		{
 			// Buffer full, flush now
@@ -2376,10 +2512,10 @@ void PhysicsSystem::JobUpdateSoftBodies(const PhysicsUpdateContext *ioContext)
 			num_bodies_to_update_bounds = 0;
 		}
 
-		if (can_sleep == ECanSleep::CanSleep)
+		if (sb_ctx->mCanSleep == ECanSleep::CanSleep)
 		{
 			// This body should go to sleep
-			bodies_to_put_to_sleep[num_bodies_to_put_to_sleep++] = b;
+			bodies_to_put_to_sleep[num_bodies_to_put_to_sleep++] = id;
 			if (num_bodies_to_put_to_sleep == cBodiesBatch)
 			{
 				mBodyManager.DeactivateBodies(bodies_to_put_to_sleep, num_bodies_to_put_to_sleep);
@@ -2395,6 +2531,9 @@ void PhysicsSystem::JobUpdateSoftBodies(const PhysicsUpdateContext *ioContext)
 	// Notify bodies to go to sleep
 	if (num_bodies_to_put_to_sleep > 0)
 		mBodyManager.DeactivateBodies(bodies_to_put_to_sleep, num_bodies_to_put_to_sleep);
+
+	// Free soft body contexts
+	ioContext->mTempAllocator->Free(ioContext->mSoftBodyUpdateContexts, ioContext->mNumSoftBodies * sizeof(SoftBodyUpdateContext));
 }
 
 void PhysicsSystem::SaveState(StateRecorder &inStream, EStateRecorderState inState, const StateRecorderFilter *inFilter) const

+ 4 - 1
Jolt/Physics/PhysicsSystem.h

@@ -213,7 +213,10 @@ private:
 	void						JobResolveCCDContacts(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep);
 	void						JobContactRemovedCallbacks(const PhysicsUpdateContext::Step *ioStep);
 	void						JobSolvePositionConstraints(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep);
-	void						JobUpdateSoftBodies(const PhysicsUpdateContext *ioContext);
+	void						JobSoftBodyPrepare(PhysicsUpdateContext *ioContext, PhysicsUpdateContext::Step *ioStep);
+	void						JobSoftBodyCollide(PhysicsUpdateContext *ioContext) const;
+	void						JobSoftBodySimulate(PhysicsUpdateContext *ioContext, uint inThreadIndex) const;
+	void						JobSoftBodyFinalize(PhysicsUpdateContext *ioContext);
 
 	/// Tries to spawn a new FindCollisions job if max concurrency hasn't been reached yet
 	void						TrySpawnJobFindCollisions(PhysicsUpdateContext::Step *ioStep) const;

+ 9 - 1
Jolt/Physics/PhysicsUpdateContext.h

@@ -17,6 +17,7 @@ class PhysicsSystem;
 class IslandBuilder;
 class Constraint;
 class TempAllocator;
+class SoftBodyUpdateContext;
 
 /// Information used during the Update call
 class PhysicsUpdateContext : public NonCopyable
@@ -130,7 +131,10 @@ public:
 		JobHandle			mResolveCCDContacts;									///< Updates the positions and velocities for all bodies that need continuous collision detection
 		JobHandleArray		mSolvePositionConstraints;								///< Solve all constraints in the position domain
 		JobHandle			mContactRemovedCallbacks;								///< Calls the contact removed callbacks
-		JobHandle			mUpdateSoftBodies;										///< Updates all soft bodies
+		JobHandle			mSoftBodyPrepare;										///< Prepares updating the soft bodies
+		JobHandleArray		mSoftBodyCollide;										///< Finds all colliding shapes for soft bodies
+		JobHandleArray		mSoftBodySimulate;										///< Simulates all particles
+		JobHandle			mSoftBodyFinalize;										///< Finalizes the soft body update
 		JobHandle			mStartNextStep;											///< Job that kicks the next step (empty for the last step)
 	};
 
@@ -155,6 +159,10 @@ public:
 	IslandBuilder *			mIslandBuilder;											///< Keeps track of connected bodies and builds islands for multithreaded velocity/position update
 
 	Steps					mSteps;
+
+	uint					mNumSoftBodies;											///< Number of active soft bodies in the simulation
+	SoftBodyUpdateContext *	mSoftBodyUpdateContexts = nullptr;						///< Contexts for updating soft bodies
+	atomic<uint>			mSoftBodyToCollide { 0 };								///< Next soft body to take when running SoftBodyCollide jobs
 };
 
 JPH_NAMESPACE_END

+ 340 - 187
Jolt/Physics/SoftBody/SoftBodyMotionProperties.cpp

@@ -68,7 +68,6 @@ void SoftBodyMotionProperties::Initialize(const SoftBodyCreationSettings &inSett
 		out_vertex.mCollidingShapeIndex = -1;
 		out_vertex.mLargestPenetration = -FLT_MAX;
 		out_vertex.mInvMass = in_vertex.mInvMass;
-		out_vertex.mProjectedDistance = 0.0f;
 		mLocalBounds.Encapsulate(out_vertex.mPosition);
 	}
 
@@ -91,44 +90,19 @@ float SoftBodyMotionProperties::GetVolumeTimesSix() const
 	return six_volume;
 }
 
-ECanSleep SoftBodyMotionProperties::Update(float inDeltaTime, Body &inSoftBody, Vec3 &outDeltaPosition, PhysicsSystem &inSystem)
+void SoftBodyMotionProperties::DetermineCollidingShapes(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem)
 {
-	// Based on: XPBD, Extended Position Based Dynamics, Matthias Muller, Ten Minute Physics
-	// See: https://matthias-research.github.io/pages/tenMinutePhysics/09-xpbd.pdf
+	JPH_PROFILE_FUNCTION();
 
-	// Convert gravity to local space
-	RMat44 body_transform = inSoftBody.GetCenterOfMassTransform();
-	Vec3 gravity = body_transform.Multiply3x3Transposed(GetGravityFactor() * inSystem.GetGravity());
-
-	// Collect information about the colliding bodies
-	struct CollidingShape
-	{
-		/// Get the velocity of a point on this body
-		Vec3			GetPointVelocity(Vec3Arg inPointRelativeToCOM) const
-		{
-			return mLinearVelocity + mAngularVelocity.Cross(inPointRelativeToCOM);
-		}
-
-		Mat44			mCenterOfMassTransform;				///< Transform of the body relative to the soft body
-		RefConst<Shape>	mShape;
-		BodyID			mBodyID;							///< Body ID of the body we hit
-		EMotionType		mMotionType;						///< Motion type of the body we hit
-		float			mInvMass;							///< Inverse mass of the body we hit
-		float			mFriction;							///< Combined friction of the two bodies
-		float			mRestitution;						///< Combined restitution of the two bodies
-		bool 			mUpdateVelocities;					///< If the linear/angular velocity changed and the body needs to be updated
-		Mat44			mInvInertia;						///< Inverse inertia in local space to the soft body
-		Vec3			mLinearVelocity;					///< Linear velocity of the body in local space to the soft body
-		Vec3			mAngularVelocity;					///< Angular velocity of the body in local space to the soft body
-	};
 	struct Collector : public CollideShapeBodyCollector
 	{
-									Collector(Body &inSoftBody, RMat44Arg inTransform, const PhysicsSystem &inSystem) :
+									Collector(Body &inSoftBody, RMat44Arg inTransform, const PhysicsSystem &inSystem, Array<CollidingShape> &ioHits) :
 										mSoftBody(inSoftBody),
 										mInverseTransform(inTransform.InversedRotationTranslation()),
 										mBodyLockInterface(inSystem.GetBodyLockInterfaceNoLock()),
 										mCombineFriction(inSystem.GetCombineFriction()),
-										mCombineRestitution(inSystem.GetCombineRestitution())
+										mCombineRestitution(inSystem.GetCombineRestitution()),
+										mHits(ioHits)
 		{
 		}
 
@@ -154,187 +128,206 @@ ECanSleep SoftBodyMotionProperties::Update(float inDeltaTime, Body &inSoftBody,
 						const MotionProperties *mp = body.GetMotionProperties();
 						cs.mInvMass = mp->GetInverseMass();
 						cs.mInvInertia = mp->GetInverseInertiaForRotation(cs.mCenterOfMassTransform.GetRotation());
-						cs.mLinearVelocity = mInverseTransform.Multiply3x3(mp->GetLinearVelocity());
-						cs.mAngularVelocity = mInverseTransform.Multiply3x3(mp->GetAngularVelocity());
+						cs.mOriginalLinearVelocity = cs.mLinearVelocity = mInverseTransform.Multiply3x3(mp->GetLinearVelocity());
+						cs.mOriginalAngularVelocity = cs.mAngularVelocity = mInverseTransform.Multiply3x3(mp->GetAngularVelocity());
 					}
 					mHits.push_back(cs);
 				}
 			}
 		}
 
+	private:
 		Body &						mSoftBody;
 		RMat44						mInverseTransform;
 		const BodyLockInterface &	mBodyLockInterface;
 		ContactConstraintManager::CombineFunction mCombineFriction;
 		ContactConstraintManager::CombineFunction mCombineRestitution;
-		Array<CollidingShape>		mHits;
+		Array<CollidingShape> &		mHits;
 	};
-	Collector collector(inSoftBody, body_transform, inSystem);
+
+	Collector collector(*inContext.mBody, inContext.mCenterOfMassTransform, inSystem, mCollidingShapes);
 	AABox bounds = mLocalBounds;
 	bounds.Encapsulate(mLocalPredictedBounds);
-	bounds = bounds.Transformed(body_transform);
-	DefaultBroadPhaseLayerFilter broadphase_layer_filter = inSystem.GetDefaultBroadPhaseLayerFilter(inSoftBody.GetObjectLayer());
-	DefaultObjectLayerFilter object_layer_filter = inSystem.GetDefaultLayerFilter(inSoftBody.GetObjectLayer());
+	bounds = bounds.Transformed(inContext.mCenterOfMassTransform);
+	ObjectLayer layer = inContext.mBody->GetObjectLayer();
+	DefaultBroadPhaseLayerFilter broadphase_layer_filter = inSystem.GetDefaultBroadPhaseLayerFilter(layer);
+	DefaultObjectLayerFilter object_layer_filter = inSystem.GetDefaultLayerFilter(layer);
 	inSystem.GetBroadPhaseQuery().CollideAABox(bounds, collector, broadphase_layer_filter, object_layer_filter);
+}
 
-	// Calculate delta time for sub step
-	float dt = inDeltaTime / mNumIterations;
-	float dt_sq = Square(dt);
-
-	// Calculate total displacement we'll have due to gravity over all sub steps
-	// The total displacement as produced by our integrator can be written as: Sum(i * g * dt^2, i = 0..mNumIterations).
-	// This is bigger than 0.5 * g * dt^2 because we first increment the velocity and then update the position
-	// Using Sum(i, i = 0..n) = n * (n + 1) / 2 we can write this as:
-	Vec3 displacement_due_to_gravity = (0.5f * mNumIterations * (mNumIterations + 1) * dt_sq) * gravity;
+void SoftBodyMotionProperties::DetermineCollisionPlanes(const SoftBodyUpdateContext &inContext, uint inVertexStart, uint inNumVertices)
+{
+	JPH_PROFILE_FUNCTION();
 
 	// Generate collision planes
-	for (const CollidingShape &cs : collector.mHits)
-		cs.mShape->CollideSoftBodyVertices(cs.mCenterOfMassTransform, Vec3::sReplicate(1.0f), mVertices, inDeltaTime, displacement_due_to_gravity, int(&cs - collector.mHits.data()));
+	for (const CollidingShape &cs : mCollidingShapes)
+		cs.mShape->CollideSoftBodyVertices(cs.mCenterOfMassTransform, Vec3::sReplicate(1.0f), mVertices.data() + inVertexStart, inNumVertices, inContext.mDeltaTime, inContext.mDisplacementDueToGravity, int(&cs - mCollidingShapes.data()));
+}
 
-	float inv_dt_sq = 1.0f / dt_sq;
-	float linear_damping = max(0.0f, 1.0f - GetLinearDamping() * dt); // See: MotionProperties::ApplyForceTorqueAndDragInternal
+void SoftBodyMotionProperties::ApplyPressure(const SoftBodyUpdateContext &inContext)
+{
+	JPH_PROFILE_FUNCTION();
 
-	for (uint iteration = 0; iteration < mNumIterations; ++iteration)
+	float dt = inContext.mSubStepDeltaTime;
+	float pressure_coefficient = mPressure;
+	if (pressure_coefficient > 0.0f)
 	{
-		float pressure_coefficient = mPressure;
-		if (pressure_coefficient > 0.0f)
+		// Calculate total volume
+		float six_volume = GetVolumeTimesSix();
+		if (six_volume > 0.0f)
 		{
-			// Calculate total volume
-			float six_volume = GetVolumeTimesSix();
-			if (six_volume > 0.0f)
+			// Apply pressure
+			// p = F / A = n R T / V (see https://en.wikipedia.org/wiki/Pressure)
+			// Our pressure coefficient is n R T so the impulse is:
+			// P = F dt = pressure_coefficient / V * A * dt
+			float coefficient = pressure_coefficient * dt / six_volume; // Need to still multiply by 6 for the volume
+			for (const Face &f : mSettings->mFaces)
 			{
-				// Apply pressure
-				// p = F / A = n R T / V (see https://en.wikipedia.org/wiki/Pressure)
-				// Our pressure coefficient is n R T so the impulse is:
-				// P = F dt = pressure_coefficient / V * A * dt
-				float coefficient = pressure_coefficient * dt / six_volume; // Need to still multiply by 6 for the volume
-				for (const Face &f : mSettings->mFaces)
-				{
-					Vec3 x1 = mVertices[f.mVertex[0]].mPosition;
-					Vec3 x2 = mVertices[f.mVertex[1]].mPosition;
-					Vec3 x3 = mVertices[f.mVertex[2]].mPosition;
+				Vec3 x1 = mVertices[f.mVertex[0]].mPosition;
+				Vec3 x2 = mVertices[f.mVertex[1]].mPosition;
+				Vec3 x3 = mVertices[f.mVertex[2]].mPosition;
 
-					Vec3 impulse = coefficient * (x2 - x1).Cross(x3 - x1); // Area is half the cross product so need to still divide by 2
-					for (uint32 i : f.mVertex)
-					{
-						Vertex &v = mVertices[i];
-						v.mVelocity += v.mInvMass * impulse; // Want to divide by 3 because we spread over 3 vertices
-					}
+				Vec3 impulse = coefficient * (x2 - x1).Cross(x3 - x1); // Area is half the cross product so need to still divide by 2
+				for (uint32 i : f.mVertex)
+				{
+					Vertex &v = mVertices[i];
+					v.mVelocity += v.mInvMass * impulse; // Want to divide by 3 because we spread over 3 vertices
 				}
 			}
 		}
+	}
+}
 
-		// Integrate
-		Vec3 sub_step_gravity = gravity * dt;
-		for (Vertex &v : mVertices)
-			if (v.mInvMass > 0.0f)
-			{
-				// Gravity
-				v.mVelocity += sub_step_gravity;
+void SoftBodyMotionProperties::IntegratePositions(const SoftBodyUpdateContext &inContext)
+{
+	JPH_PROFILE_FUNCTION();
 
-				// Damping
-				v.mVelocity *= linear_damping;
+	float dt = inContext.mSubStepDeltaTime;
+	float linear_damping = max(0.0f, 1.0f - GetLinearDamping() * dt); // See: MotionProperties::ApplyForceTorqueAndDragInternal
 
-				// Integrate
-				v.mPreviousPosition = v.mPosition;
-				v.mPosition += v.mVelocity * dt;
+	// Integrate
+	Vec3 sub_step_gravity = inContext.mGravity * dt;
+	for (Vertex &v : mVertices)
+		if (v.mInvMass > 0.0f)
+		{
+			// Gravity
+			v.mVelocity += sub_step_gravity;
 
-				// Reset projected distance
-				v.mProjectedDistance = 0.0f;
-			}
-			else
-			{
-				// Integrate
-				v.mPreviousPosition = v.mPosition;
-				v.mPosition += v.mVelocity * dt;
-			}
+			// Damping
+			v.mVelocity *= linear_damping;
 
-		// Satisfy volume constraints
-		for (const Volume &v : mSettings->mVolumeConstraints)
+			// Integrate
+			v.mPreviousPosition = v.mPosition;
+			v.mPosition += v.mVelocity * dt;
+		}
+		else
 		{
-			Vertex &v1 = mVertices[v.mVertex[0]];
-			Vertex &v2 = mVertices[v.mVertex[1]];
-			Vertex &v3 = mVertices[v.mVertex[2]];
-			Vertex &v4 = mVertices[v.mVertex[3]];
-
-			Vec3 x1 = v1.mPosition;
-			Vec3 x2 = v2.mPosition;
-			Vec3 x3 = v3.mPosition;
-			Vec3 x4 = v4.mPosition;
-
-			// Calculate constraint equation
-			Vec3 x1x2 = x2 - x1;
-			Vec3 x1x3 = x3 - x1;
-			Vec3 x1x4 = x4 - x1;
-			float c = abs(x1x2.Cross(x1x3).Dot(x1x4)) - v.mSixRestVolume;
-
-			// Calculate gradient of constraint equation
-			Vec3 d1c = (x4 - x2).Cross(x3 - x2);
-			Vec3 d2c = x1x3.Cross(x1x4);
-			Vec3 d3c = x1x4.Cross(x1x2);
-			Vec3 d4c = x1x2.Cross(x1x3);
-
-			float w1 = v1.mInvMass;
-			float w2 = v2.mInvMass;
-			float w3 = v3.mInvMass;
-			float w4 = v4.mInvMass;
-			JPH_ASSERT(w1 > 0.0f || w2 > 0.0f || w3 > 0.0f || w4 > 0.0f);
+			// Integrate
+			v.mPreviousPosition = v.mPosition;
+			v.mPosition += v.mVelocity * dt;
+		}
+}
 
+void SoftBodyMotionProperties::ApplyVolumeConstraints(const SoftBodyUpdateContext &inContext)
+{
+	JPH_PROFILE_FUNCTION();
+
+	float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
+
+	// Satisfy volume constraints
+	for (const Volume &v : mSettings->mVolumeConstraints)
+	{
+		Vertex &v1 = mVertices[v.mVertex[0]];
+		Vertex &v2 = mVertices[v.mVertex[1]];
+		Vertex &v3 = mVertices[v.mVertex[2]];
+		Vertex &v4 = mVertices[v.mVertex[3]];
+
+		Vec3 x1 = v1.mPosition;
+		Vec3 x2 = v2.mPosition;
+		Vec3 x3 = v3.mPosition;
+		Vec3 x4 = v4.mPosition;
+
+		// Calculate constraint equation
+		Vec3 x1x2 = x2 - x1;
+		Vec3 x1x3 = x3 - x1;
+		Vec3 x1x4 = x4 - x1;
+		float c = abs(x1x2.Cross(x1x3).Dot(x1x4)) - v.mSixRestVolume;
+
+		// Calculate gradient of constraint equation
+		Vec3 d1c = (x4 - x2).Cross(x3 - x2);
+		Vec3 d2c = x1x3.Cross(x1x4);
+		Vec3 d3c = x1x4.Cross(x1x2);
+		Vec3 d4c = x1x2.Cross(x1x3);
+
+		float w1 = v1.mInvMass;
+		float w2 = v2.mInvMass;
+		float w3 = v3.mInvMass;
+		float w4 = v4.mInvMass;
+		JPH_ASSERT(w1 > 0.0f || w2 > 0.0f || w3 > 0.0f || w4 > 0.0f);
+
+		// Apply correction
+		float lambda = -c / (w1 * d1c.LengthSq() + w2 * d2c.LengthSq() + w3 * d3c.LengthSq() + w4 * d4c.LengthSq() + v.mCompliance * inv_dt_sq);
+		v1.mPosition += lambda * w1 * d1c;
+		v2.mPosition += lambda * w2 * d2c;
+		v3.mPosition += lambda * w3 * d3c;
+		v4.mPosition += lambda * w4 * d4c;
+	}
+}
+
+void SoftBodyMotionProperties::ApplyEdgeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex)
+{
+	JPH_PROFILE_FUNCTION();
+
+	float inv_dt_sq = 1.0f / Square(inContext.mSubStepDeltaTime);
+
+	// Satisfy edge constraints
+	const Array<Edge> &edge_constraints = mSettings->mEdgeConstraints;
+	for (uint i = inStartIndex; i < inEndIndex; ++i)
+	{
+		const Edge &e = edge_constraints[i];
+		Vertex &v0 = mVertices[e.mVertex[0]];
+		Vertex &v1 = mVertices[e.mVertex[1]];
+
+		// Calculate current length
+		Vec3 delta = v1.mPosition - v0.mPosition;
+		float length = delta.Length();
+		if (length > 0.0f)
+		{
 			// Apply correction
-			float lambda = -c / (w1 * d1c.LengthSq() + w2 * d2c.LengthSq() + w3 * d3c.LengthSq() + w4 * d4c.LengthSq() + v.mCompliance * inv_dt_sq);
-			v1.mPosition += lambda * w1 * d1c;
-			v2.mPosition += lambda * w2 * d2c;
-			v3.mPosition += lambda * w3 * d3c;
-			v4.mPosition += lambda * w4 * d4c;
+			Vec3 correction = delta * (length - e.mRestLength) / (length * (v0.mInvMass + v1.mInvMass + e.mCompliance * inv_dt_sq));
+			v0.mPosition += v0.mInvMass * correction;
+			v1.mPosition -= v1.mInvMass * correction;
 		}
+	}
+}
+
+void SoftBodyMotionProperties::ApplyCollisionConstraintsAndUpdateVelocities(const SoftBodyUpdateContext &inContext)
+{
+	JPH_PROFILE_FUNCTION();
 
-		// Satisfy edge constraints
-		for (const Edge &e : mSettings->mEdgeConstraints)
+	float dt = inContext.mSubStepDeltaTime;
+	float restitution_treshold = -2.0f * inContext.mGravity.Length() * dt;
+	for (Vertex &v : mVertices)
+		if (v.mInvMass > 0.0f)
 		{
-			Vertex &v0 = mVertices[e.mVertex[0]];
-			Vertex &v1 = mVertices[e.mVertex[1]];
+			// Remember previous velocity for restitution calculations
+			Vec3 prev_v = v.mVelocity;
 
-			// Calculate current length
-			Vec3 delta = v1.mPosition - v0.mPosition;
-			float length = delta.Length();
-			if (length > 0.0f)
-			{
-				// Apply correction
-				Vec3 correction = delta * (length - e.mRestLength) / (length * (v0.mInvMass + v1.mInvMass + e.mCompliance * inv_dt_sq));
-				v0.mPosition += v0.mInvMass * correction;
-				v1.mPosition -= v1.mInvMass * correction;
-			}
-		}
+			// XPBD velocity update
+			v.mVelocity = (v.mPosition - v.mPreviousPosition) / dt;
 
-		// Satisfy collision
-		for (Vertex &v : mVertices)
+			// Satisfy collision constraint
 			if (v.mCollidingShapeIndex >= 0)
 			{
-				float distance = v.mCollisionPlane.SignedDistance(v.mPosition);
-				if (distance < 0.0f)
+				// Check if there is a collision
+				float projected_distance = -v.mCollisionPlane.SignedDistance(v.mPosition);
+				if (projected_distance > 0.0f)
 				{
-					Vec3 delta = v.mCollisionPlane.GetNormal() * distance;
-					v.mPosition -= delta;
-					v.mPreviousPosition -= delta; // Apply delta to previous position so that we will not accumulate velocity by being pushed out of collision
-					v.mProjectedDistance -= distance; // For friction calculation
-				}
-			}
-
-		// Update velocity
-		float restitution_treshold = -2.0f * gravity.Length() * dt;
-		for (Vertex &v : mVertices)
-			if (v.mInvMass > 0.0f)
-			{
-				Vec3 prev_v = v.mVelocity;
-
-				// XPBD velocity update
-				v.mVelocity = (v.mPosition - v.mPreviousPosition) / dt;
+					// Note that we already calculated the velocity, so this does not affect the velocity (next iteration starts by setting previous position to current position)
+					Vec3 contact_normal = v.mCollisionPlane.GetNormal();
+					v.mPosition += contact_normal * projected_distance;
 
-				// If there was a collision
-				if (v.mProjectedDistance > 0.0f)
-				{
-					JPH_ASSERT(v.mCollidingShapeIndex >= 0);
-					CollidingShape &cs = collector.mHits[v.mCollidingShapeIndex];
+					CollidingShape &cs = mCollidingShapes[v.mCollidingShapeIndex];
 
 					// Apply friction as described in Detailed Rigid Body Simulation with Extended Position Based Dynamics - Matthias Muller et al.
 					// See section 3.6:
@@ -353,7 +346,6 @@ ECanSleep SoftBodyMotionProperties::Update(float inDeltaTime, Body &inSoftBody,
 					// v1 = v1 + p / m1
 					// v2 = v2 - p / m2 (no change when colliding with a static body)
 					// w2 = w2 - I^-1 (r2 x p) (no change when colliding with a static body)
-					Vec3 contact_normal = v.mCollisionPlane.GetNormal();
 					if (cs.mMotionType == EMotionType::Dynamic)
 					{
 						// Calculate normal and tangential velocity (equation 30)
@@ -372,7 +364,7 @@ ECanSleep SoftBodyMotionProperties::Update(float inDeltaTime, Body &inSoftBody,
 						// Calculate delta relative velocity due to friction (modified equation 31)
 						Vec3 dv;
 						if (v_tangential_length > 0.0f)
-							dv = v_tangential * min(cs.mFriction * v.mProjectedDistance / (v_tangential_length * dt), 1.0f);
+							dv = v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f);
 						else
 							dv = Vec3::sZero();
 
@@ -406,7 +398,7 @@ ECanSleep SoftBodyMotionProperties::Update(float inDeltaTime, Body &inSoftBody,
 
 						// Apply friction (modified equation 31)
 						if (v_tangential_length > 0.0f)
-							v.mVelocity -= v_tangential * min(cs.mFriction * v.mProjectedDistance / (v_tangential_length * dt), 1.0f);
+							v.mVelocity -= v_tangential * min(cs.mFriction * projected_distance / (v_tangential_length * dt), 1.0f);
 
 						// Apply restitution (equation 35)
 						v.mVelocity -= v_normal;
@@ -417,8 +409,14 @@ ECanSleep SoftBodyMotionProperties::Update(float inDeltaTime, Body &inSoftBody,
 				}
 			}
 		}
+}
+
+void SoftBodyMotionProperties::UpdateSoftBodyState(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings)
+{
+	JPH_PROFILE_FUNCTION();
 
 	// Loop through vertices once more to update the global state
+	float dt = ioContext.mDeltaTime;
 	float max_linear_velocity_sq = Square(GetMaxLinearVelocity());
 	float max_v_sq = 0.0f;
 	Vec3 linear_velocity = Vec3::sZero(), angular_velocity = Vec3::sZero();
@@ -441,7 +439,7 @@ ECanSleep SoftBodyMotionProperties::Update(float inDeltaTime, Body &inSoftBody,
 		mLocalBounds.Encapsulate(v.mPosition);
 
 		// Create predicted position for the next frame in order to detect collisions before they happen
-		mLocalPredictedBounds.Encapsulate(v.mPosition + v.mVelocity * inDeltaTime + displacement_due_to_gravity);
+		mLocalPredictedBounds.Encapsulate(v.mPosition + v.mVelocity * dt + ioContext.mDisplacementDueToGravity);
 
 		// Reset collision data for the next iteration
 		v.mCollidingShapeIndex = -1;
@@ -450,14 +448,14 @@ ECanSleep SoftBodyMotionProperties::Update(float inDeltaTime, Body &inSoftBody,
 
 	// 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));
-	SetLinearVelocity(body_transform.Multiply3x3(linear_velocity / num_vertices_divider));
-	SetAngularVelocity(body_transform.Multiply3x3(angular_velocity / num_vertices_divider));
+	SetLinearVelocity(ioContext.mCenterOfMassTransform.Multiply3x3(linear_velocity / num_vertices_divider));
+	SetAngularVelocity(ioContext.mCenterOfMassTransform.Multiply3x3(angular_velocity / num_vertices_divider));
 
 	if (mUpdatePosition)
 	{
 		// Shift the body so that the position is the center of the local bounds
 		Vec3 delta = mLocalBounds.GetCenter();
-		outDeltaPosition = body_transform.Multiply3x3(delta);
+		ioContext.mDeltaPosition = ioContext.mCenterOfMassTransform.Multiply3x3(delta);
 		for (Vertex &v : mVertices)
 			v.mPosition -= delta;
 
@@ -466,26 +464,181 @@ ECanSleep SoftBodyMotionProperties::Update(float inDeltaTime, Body &inSoftBody,
 		mLocalPredictedBounds.Translate(-delta);
 	}
 	else
-		outDeltaPosition = Vec3::sZero();
+		ioContext.mDeltaPosition = Vec3::sZero();
+
+	// Test if we should go to sleep
+	if (GetAllowSleeping())
+	{
+		if (max_v_sq > inPhysicsSettings.mPointVelocitySleepThreshold)
+		{
+			ResetSleepTestTimer();
+			ioContext.mCanSleep = ECanSleep::CannotSleep;
+		}
+		else
+			ioContext.mCanSleep = AccumulateSleepTime(dt, inPhysicsSettings.mTimeBeforeSleep);
+	}
+	else
+		ioContext.mCanSleep = ECanSleep::CannotSleep;
+}
+
+void SoftBodyMotionProperties::UpdateRigidBodyVelocities(const SoftBodyUpdateContext &inContext, PhysicsSystem &inSystem)
+{
+	JPH_PROFILE_FUNCTION();
 
-	// Write back velocities
+	// Write back velocity deltas
 	BodyInterface &body_interface = inSystem.GetBodyInterfaceNoLock();
-	for (const CollidingShape &cs : collector.mHits)
+	for (const CollidingShape &cs : mCollidingShapes)
 		if (cs.mUpdateVelocities)
-			body_interface.SetLinearAndAngularVelocity(cs.mBodyID, body_transform.Multiply3x3(cs.mLinearVelocity), body_transform.Multiply3x3(cs.mAngularVelocity));
+			body_interface.AddLinearAndAngularVelocity(cs.mBodyID, inContext.mCenterOfMassTransform.Multiply3x3(cs.mLinearVelocity - cs.mOriginalLinearVelocity), inContext.mCenterOfMassTransform.Multiply3x3(cs.mAngularVelocity - cs.mOriginalAngularVelocity));
 
-	// Test if we should go to sleep
-	if (!GetAllowSleeping())
-		return ECanSleep::CannotSleep;
+	// Clear colliding shapes to avoid hanging on to references to shapes
+	mCollidingShapes.clear();
+}
+
+void SoftBodyMotionProperties::InitializeUpdateContext(float inDeltaTime, Body &inSoftBody, const PhysicsSystem &inSystem, SoftBodyUpdateContext &ioContext)
+{
+	JPH_PROFILE_FUNCTION();
+
+	// Store body
+	ioContext.mBody = &inSoftBody;
+	ioContext.mMotionProperties = this;
+
+	// Convert gravity to local space
+	ioContext.mCenterOfMassTransform = inSoftBody.GetCenterOfMassTransform();
+	ioContext.mGravity = ioContext.mCenterOfMassTransform.Multiply3x3Transposed(GetGravityFactor() * inSystem.GetGravity());
+
+	// Calculate delta time for sub step
+	ioContext.mDeltaTime = inDeltaTime;
+	ioContext.mSubStepDeltaTime = inDeltaTime / mNumIterations;
+
+	// Calculate total displacement we'll have due to gravity over all sub steps
+	// The total displacement as produced by our integrator can be written as: Sum(i * g * dt^2, i = 0..mNumIterations).
+	// This is bigger than 0.5 * g * dt^2 because we first increment the velocity and then update the position
+	// Using Sum(i, i = 0..n) = n * (n + 1) / 2 we can write this as:
+	ioContext.mDisplacementDueToGravity = (0.5f * mNumIterations * (mNumIterations + 1) * Square(ioContext.mSubStepDeltaTime)) * ioContext.mGravity;
+}
+
+void SoftBodyMotionProperties::StartNextIteration(const SoftBodyUpdateContext &ioContext)
+{
+	ApplyPressure(ioContext);
 
-	const PhysicsSettings &physics_settings = inSystem.GetPhysicsSettings();
-	if (max_v_sq > physics_settings.mPointVelocitySleepThreshold)
+	IntegratePositions(ioContext);
+
+	ApplyVolumeConstraints(ioContext);
+}
+
+SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelDetermineCollisionPlanes(SoftBodyUpdateContext &ioContext)
+{
+	// Do a relaxed read first to see if there is any work to do (this prevents us from doing expensive atomic operations and also prevents us from continuously incrementing the counter and overflowing it)
+	uint num_vertices = (uint)mVertices.size();
+	if (ioContext.mNextCollisionVertex.load(memory_order_relaxed) < num_vertices)
 	{
-		ResetSleepTestTimer();
-		return ECanSleep::CannotSleep;
+		// Fetch next batch of vertices to process
+		uint next_vertex = ioContext.mNextCollisionVertex.fetch_add(SoftBodyUpdateContext::cVertexCollisionBatch, memory_order_acquire);
+		if (next_vertex < num_vertices)
+		{
+			// Process collision planes
+			uint num_vertices_to_process = min(SoftBodyUpdateContext::cVertexCollisionBatch, num_vertices - next_vertex);
+			DetermineCollisionPlanes(ioContext, next_vertex, num_vertices_to_process);
+			uint vertices_processed = ioContext.mNumCollisionVerticesProcessed.fetch_add(SoftBodyUpdateContext::cVertexCollisionBatch, memory_order_release) + num_vertices_to_process;
+			if (vertices_processed >= num_vertices)
+			{
+				// Start the first iteration
+				JPH_IF_ENABLE_ASSERTS(uint iteration =) ioContext.mNextIteration.fetch_add(1, memory_order_relaxed);
+				JPH_ASSERT(iteration == 0);
+				StartNextIteration(ioContext);
+				ioContext.mState.store(SoftBodyUpdateContext::EState::ApplyEdgeConstraints, memory_order_release);
+			}
+			return EStatus::DidWork;
+		}
 	}
+	return EStatus::NoWork;
+}
+
+SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelApplyEdgeConstraints(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings)
+{
+	// Do a relaxed read first to see if there is any work to do (this prevents us from doing expensive atomic operations and also prevents us from continuously incrementing the counter and overflowing it)
+	uint num_groups = (uint)mSettings->mEdgeGroupEndIndices.size();
+	JPH_ASSERT(num_groups > 0, "SoftBodySharedSettings::Optimize should have been called!");
+	uint32 edge_group, edge_start_idx;
+	SoftBodyUpdateContext::sGetEdgeGroupAndStartIdx(ioContext.mNextEdgeConstraint.load(memory_order_relaxed), edge_group, edge_start_idx);
+	if (edge_group < num_groups && edge_start_idx < mSettings->GetEdgeGroupSize(edge_group))
+	{
+		// Fetch the next batch of edges to process
+		uint64 next_edge_batch = ioContext.mNextEdgeConstraint.fetch_add(SoftBodyUpdateContext::cEdgeConstraintBatch, memory_order_acquire);
+		SoftBodyUpdateContext::sGetEdgeGroupAndStartIdx(next_edge_batch, edge_group, edge_start_idx);
+		if (edge_group < num_groups)
+		{
+			bool non_parallel_group = edge_group == num_groups - 1; // Last group is the non-parallel group and goes as a whole
+			uint edge_group_size = mSettings->GetEdgeGroupSize(edge_group);
+			if (non_parallel_group? edge_start_idx == 0 : edge_start_idx < edge_group_size)
+			{
+				// Process edges
+				uint num_edges_to_process = non_parallel_group? edge_group_size : min(SoftBodyUpdateContext::cEdgeConstraintBatch, edge_group_size - edge_start_idx);
+				if (edge_group > 0)
+					edge_start_idx += mSettings->mEdgeGroupEndIndices[edge_group - 1];
+				ApplyEdgeConstraints(ioContext, edge_start_idx, edge_start_idx + num_edges_to_process);
+
+				// Test if we're at the end of this group
+				uint edge_constraints_processed = ioContext.mNumEdgeConstraintsProcessed.fetch_add(num_edges_to_process, memory_order_relaxed) + num_edges_to_process;
+				if (edge_constraints_processed >= edge_group_size)
+				{
+					// Non parallel group is the last group (which is also the only group that can be empty)
+					if (non_parallel_group || mSettings->GetEdgeGroupSize(edge_group + 1) == 0)
+					{
+						// Finish the iteration
+						ApplyCollisionConstraintsAndUpdateVelocities(ioContext);
+
+						uint iteration = ioContext.mNextIteration.fetch_add(1, memory_order_relaxed);
+						if (iteration < mNumIterations)
+						{
+							// Start a new iteration
+							StartNextIteration(ioContext);
+
+							// Reset next edge to process
+							ioContext.mNumEdgeConstraintsProcessed.store(0, memory_order_relaxed);
+							ioContext.mNextEdgeConstraint.store(0, memory_order_release);
+						}
+						else
+						{
+							// On final iteration we update the state
+							UpdateSoftBodyState(ioContext, inPhysicsSettings);
 
-	return AccumulateSleepTime(inDeltaTime, physics_settings.mTimeBeforeSleep);
+							ioContext.mState.store(SoftBodyUpdateContext::EState::Done, memory_order_release);
+							return EStatus::Done;
+						}
+					}
+					else
+					{
+						// Next group
+						ioContext.mNumEdgeConstraintsProcessed.store(0, memory_order_relaxed);
+						ioContext.mNextEdgeConstraint.store(SoftBodyUpdateContext::sGetEdgeGroupStart(edge_group + 1), memory_order_release);
+					}
+				}
+				return EStatus::DidWork;
+			}
+		}
+	}
+	return EStatus::NoWork;
+}
+
+SoftBodyMotionProperties::EStatus SoftBodyMotionProperties::ParallelUpdate(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings)
+{
+	switch (ioContext.mState.load(memory_order_relaxed))
+	{
+	case SoftBodyUpdateContext::EState::DetermineCollisionPlanes:
+		return ParallelDetermineCollisionPlanes(ioContext);
+
+	case SoftBodyUpdateContext::EState::ApplyEdgeConstraints:
+		return ParallelApplyEdgeConstraints(ioContext, inPhysicsSettings);
+
+	case SoftBodyUpdateContext::EState::Done:
+		return EStatus::Done;
+
+	default:
+		JPH_ASSERT(false);
+		return EStatus::NoWork;
+	}
 }
 
 #ifdef JPH_DEBUG_RENDERER

+ 83 - 4
Jolt/Physics/SoftBody/SoftBodyMotionProperties.h

@@ -5,20 +5,27 @@
 #pragma once
 
 #include <Jolt/Geometry/AABox.h>
+#include <Jolt/Physics/Body/BodyID.h>
 #include <Jolt/Physics/Body/MotionProperties.h>
 #include <Jolt/Physics/SoftBody/SoftBodySharedSettings.h>
 #include <Jolt/Physics/SoftBody/SoftBodyVertex.h>
+#include <Jolt/Physics/SoftBody/SoftBodyUpdateContext.h>
 
 JPH_NAMESPACE_BEGIN
 
 class PhysicsSystem;
+struct PhysicsSettings;
 class Body;
+class Shape;
 class SoftBodyCreationSettings;
 #ifdef JPH_DEBUG_RENDERER
 class DebugRenderer;
 #endif // JPH_DEBUG_RENDERER
 
-/// This class contains the runtime information of a soft body. Soft bodies are implemented using XPBD, a particle and springs based approach.
+/// This class contains the runtime information of a soft body.
+//
+// Based on: XPBD, Extended Position Based Dynamics, Matthias Muller, Ten Minute Physics
+// See: https://matthias-research.github.io/pages/tenMinutePhysics/09-xpbd.pdf
 class JPH_EXPORT SoftBodyMotionProperties : public MotionProperties
 {
 public:
@@ -30,9 +37,6 @@ public:
 	/// Initialize the soft body motion properties
 	void								Initialize(const SoftBodyCreationSettings &inSettings);
 
-	/// Update the soft body
-	ECanSleep							Update(float inDeltaTime, Body &inSoftBody, Vec3 &outDeltaPosition, PhysicsSystem &inSystem);
-
 	/// Get the shared settings of the soft body
 	const SoftBodySharedSettings *		GetSettings() const							{ return mSettings; }
 
@@ -88,12 +92,87 @@ public:
 	/// Restoring state for replay
 	void								RestoreState(StateRecorder &inStream);
 
+	/// Initialize the update context (used internally by the PhysicsSystem)
+	void								InitializeUpdateContext(float inDeltaTime, Body &inSoftBody, const PhysicsSystem &inSystem, SoftBodyUpdateContext &ioContext);
+
+	/// Do a broad phase check and collect all bodies that can possibly collide with this soft body
+	void								DetermineCollidingShapes(const SoftBodyUpdateContext &inContext, const PhysicsSystem &inSystem);
+
+	/// Return code for ParallelUpdate
+	enum class EStatus
+	{
+		NoWork	= 1 << 0,				///< No work was done because other threads were still working on a batch that cannot run concurrently
+		DidWork	= 1 << 1,				///< Work was done to progress the update
+		Done	= 1 << 2,				///< All work is done
+	};
+
+	/// Update the soft body, will process a batch of work. Used internally.
+	EStatus								ParallelUpdate(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings);
+
+	/// Update the velocities of all rigid bodies that we collided with. Used internally.
+	void								UpdateRigidBodyVelocities(const SoftBodyUpdateContext &inContext, PhysicsSystem &inSystem);
+
 private:
+	// Collect information about the colliding bodies
+	struct CollidingShape
+	{
+		/// Get the velocity of a point on this body
+		Vec3			GetPointVelocity(Vec3Arg inPointRelativeToCOM) const
+		{
+			return mLinearVelocity + mAngularVelocity.Cross(inPointRelativeToCOM);
+		}
+
+		Mat44							mCenterOfMassTransform;						///< Transform of the body relative to the soft body
+		RefConst<Shape>					mShape;										///< Shape of the body we hit
+		BodyID							mBodyID;									///< Body ID of the body we hit
+		EMotionType						mMotionType;								///< Motion type of the body we hit
+		float							mInvMass;									///< Inverse mass of the body we hit
+		float							mFriction;									///< Combined friction of the two bodies
+		float							mRestitution;								///< Combined restitution of the two bodies
+		bool 							mUpdateVelocities;							///< If the linear/angular velocity changed and the body needs to be updated
+		Mat44							mInvInertia;								///< Inverse inertia in local space to the soft body
+		Vec3							mLinearVelocity;							///< Linear velocity of the body in local space to the soft body
+		Vec3							mAngularVelocity;							///< Angular velocity of the body in local space to the soft body
+		Vec3							mOriginalLinearVelocity;					///< Linear velocity of the body in local space to the soft body at start
+		Vec3							mOriginalAngularVelocity;					///< Angular velocity of the body in local space to the soft body at start
+	};
+
+	/// Do a narrow phase check and determine the closest feature that we can collide with
+	void								DetermineCollisionPlanes(const SoftBodyUpdateContext &inContext, uint inVertexStart, uint inNumVertices);
+
+	/// Apply pressure force and update the vertex velocities
+	void								ApplyPressure(const SoftBodyUpdateContext &inContext);
+
+	/// Integrate the positions of all vertices by 1 sub step
+	void								IntegratePositions(const SoftBodyUpdateContext &inContext);
+
+	/// Enforce all volume constraints
+	void								ApplyVolumeConstraints(const SoftBodyUpdateContext &inContext);
+
+	/// Enforce all edge constraints
+	void								ApplyEdgeConstraints(const SoftBodyUpdateContext &inContext, uint inStartIndex, uint inEndIndex);
+
+	/// Enforce all collision constraints & update all velocities according the the XPBD algorithm
+	void								ApplyCollisionConstraintsAndUpdateVelocities(const SoftBodyUpdateContext &inContext);
+
+	/// Update the state of the soft body (position, velocity, bounds)
+	void								UpdateSoftBodyState(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings);
+
+	/// Executes tasks that need to run on the start of an iteration (i.e. the stuff that can't run in parallel)
+	void								StartNextIteration(const SoftBodyUpdateContext &ioContext);
+
+	/// Helper function for ParallelUpdate that works on batches of collision planes
+	EStatus								ParallelDetermineCollisionPlanes(SoftBodyUpdateContext &ioContext);
+
+	/// Helper function for ParallelUpdate that works on batches of edges
+	EStatus								ParallelApplyEdgeConstraints(SoftBodyUpdateContext &ioContext, const PhysicsSettings &inPhysicsSettings);
+
 	/// Returns 6 times the volume of the soft body
 	float								GetVolumeTimesSix() const;
 
 	RefConst<SoftBodySharedSettings>	mSettings;									///< Configuration of the particles and constraints
 	Array<Vertex>						mVertices;									///< Current state of all vertices in the simulation
+	Array<CollidingShape>				mCollidingShapes;							///< List of colliding shapes retrieved during the last update
 	AABox								mLocalBounds;								///< Bounding box of all vertices
 	AABox								mLocalPredictedBounds;						///< Predicted bounding box for all vertices using extrapolation of velocity by last step delta time
 	uint32								mNumIterations;								///< Number of solver iterations

+ 1 - 1
Jolt/Physics/SoftBody/SoftBodyShape.cpp

@@ -106,7 +106,7 @@ void SoftBodyShape::CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSub
 	sCollidePointUsingRayCast(*this, inPoint, inSubShapeIDCreator, ioCollector, inShapeFilter);
 }
 
-void SoftBodyShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
+void SoftBodyShape::CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const
 {
 	/* Not implemented */
 }

+ 1 - 1
Jolt/Physics/SoftBody/SoftBodyShape.h

@@ -44,7 +44,7 @@ public:
 	virtual bool					CastRay(const RayCast &inRay, const SubShapeIDCreator &inSubShapeIDCreator, RayCastResult &ioHit) const override;
 	virtual void					CastRay(const RayCast &inRay, const RayCastSettings &inRayCastSettings, const SubShapeIDCreator &inSubShapeIDCreator, CastRayCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
 	virtual void					CollidePoint(Vec3Arg inPoint, const SubShapeIDCreator &inSubShapeIDCreator, CollidePointCollector &ioCollector, const ShapeFilter &inShapeFilter = { }) const override;
-	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, Array<SoftBodyVertex> &ioVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
+	virtual void					CollideSoftBodyVertices(Mat44Arg inCenterOfMassTransform, Vec3Arg inScale, SoftBodyVertex *ioVertices, uint inNumVertices, float inDeltaTime, Vec3Arg inDisplacementDueToGravity, int inCollidingShapeIndex) const override;
 	virtual void					GetTrianglesStart(GetTrianglesContext &ioContext, const AABox &inBox, Vec3Arg inPositionCOM, QuatArg inRotation, Vec3Arg inScale) const override;
 	virtual int						GetTrianglesNext(GetTrianglesContext &ioContext, int inMaxTrianglesRequested, Float3 *outTriangleVertices, const PhysicsMaterial **outMaterials = nullptr) const override;
 	virtual Stats					GetStats() const override;

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

@@ -5,9 +5,11 @@
 #include <Jolt/Jolt.h>
 
 #include <Jolt/Physics/SoftBody/SoftBodySharedSettings.h>
+#include <Jolt/Physics/SoftBody/SoftBodyUpdateContext.h>
 #include <Jolt/ObjectStream/TypeDeclarations.h>
 #include <Jolt/Core/StreamIn.h>
 #include <Jolt/Core/StreamOut.h>
+#include <Jolt/Core/QuickSort.h>
 
 JPH_NAMESPACE_BEGIN
 
@@ -73,6 +75,65 @@ void SoftBodySharedSettings::CalculateVolumeConstraintVolumes()
 	}
 }
 
+void SoftBodySharedSettings::Optimize(OptimizationResults &outResults)
+{
+	const uint cMaxNumGroups = 32;
+	const uint cNonParallelGroupIdx = cMaxNumGroups - 1;
+	const uint cMinimumSize = 2 * SoftBodyUpdateContext::cEdgeConstraintBatch; // There should be at least 2 batches, otherwise there's no point in parallelizing
+
+	// Assign edges to non-overlapping groups
+	Array<uint32> masks;
+	masks.resize(mVertices.size(), 0);
+	Array<uint> edge_groups[cMaxNumGroups];
+	for (const Edge &e : mEdgeConstraints)
+	{
+		uint32 &mask1 = masks[e.mVertex[0]];
+		uint32 &mask2 = masks[e.mVertex[1]];
+		uint group = min(CountTrailingZeros((~mask1) & (~mask2)), cNonParallelGroupIdx);
+		uint32 mask = uint32(1U << group);
+		mask1 |= mask;
+		mask2 |= mask;
+		edge_groups[group].push_back(uint(&e - mEdgeConstraints.data()));
+	}
+
+	// Merge groups that are too small into the non-parallel group
+	for (uint i = 0; i < cNonParallelGroupIdx; ++i)
+		if (edge_groups[i].size() < cMinimumSize)
+		{
+			edge_groups[cNonParallelGroupIdx].insert(edge_groups[cNonParallelGroupIdx].end(), edge_groups[i].begin(), edge_groups[i].end());
+			edge_groups[i].clear();
+		}
+
+	// Order the edges so that the ones with the smallest index go first (hoping to get better cache locality when we process the edges).
+	// Note we could also re-order the vertices but that would be much more of a burden to the end user
+	for (Array<uint> &group : edge_groups)
+		QuickSort(group.begin(), group.end(), [this](uint inLHS, uint inRHS)
+			{
+				const Edge &e1 = mEdgeConstraints[inLHS];
+				const Edge &e2 = mEdgeConstraints[inRHS];
+				return min(e1.mVertex[0], e1.mVertex[1]) < min(e2.mVertex[0], e2.mVertex[1]);
+			});
+
+	// Assign the edges to groups and reorder them
+	Array<Edge> temp_edges;
+	temp_edges.swap(mEdgeConstraints);
+	mEdgeConstraints.reserve(temp_edges.size());
+	for (const Array<uint> &group : edge_groups)
+		if (!group.empty())
+		{
+			for (uint idx : group)
+			{
+				mEdgeConstraints.push_back(temp_edges[idx]);
+				outResults.mEdgeRemap.push_back(idx);
+			}
+			mEdgeGroupEndIndices.push_back((uint)mEdgeConstraints.size());
+		}
+
+	// If there is no non-parallel group then add an empty group at the end
+	if (edge_groups[cNonParallelGroupIdx].empty())
+		mEdgeGroupEndIndices.push_back((uint)mEdgeConstraints.size());
+}
+
 void SoftBodySharedSettings::SaveBinaryState(StreamOut &inStream) const
 {
 	inStream.Write(mVertices);

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

@@ -23,6 +23,16 @@ public:
 	/// Calculates the initial volume of all tetrahedra of this soft body
 	void				CalculateVolumeConstraintVolumes();
 
+	/// Information about the optimization of the soft body, the indices of certain elements may have changed.
+	class OptimizationResults
+	{
+	public:
+		Array<uint>		mEdgeRemap;									///< Maps old edge index to new edge index
+	};
+
+	/// Optimize the soft body settings for simulation. This will reorder constraints so they can be executed in parallel.
+	void				Optimize(OptimizationResults &outResults);
+
 	/// Saves the state of this object in binary form to inStream. Doesn't store the material list.
 	void				SaveBinaryState(StreamOut &inStream) const;
 
@@ -103,9 +113,13 @@ public:
 	/// Add a face to this soft body
 	void				AddFace(const Face &inFace)					{ JPH_ASSERT(!inFace.IsDegenerate()); mFaces.push_back(inFace); }
 
+	/// Get the size of an edge group (edge groups can run in parallel)
+	uint				GetEdgeGroupSize(uint inGroupIdx) const		{ return inGroupIdx == 0? mEdgeGroupEndIndices[0] : mEdgeGroupEndIndices[inGroupIdx] - mEdgeGroupEndIndices[inGroupIdx - 1]; }
+
 	Array<Vertex>		mVertices;									///< The list of vertices or particles of the body
 	Array<Face>			mFaces;										///< The list of faces of the body
 	Array<Edge>			mEdgeConstraints;							///< The list of edges or springs of the body
+	Array<uint>			mEdgeGroupEndIndices;						///< The start index of each group of edges that can be solved in parallel
 	Array<Volume>		mVolumeConstraints;							///< The list of volume constraints of the body that keep the volume of tetrahedra in the soft body constant
 	PhysicsMaterialList mMaterials { PhysicsMaterial::sDefault };	///< The materials of the faces of the body, referenced by Face::mMaterialIndex
 };

+ 65 - 0
Jolt/Physics/SoftBody/SoftBodyUpdateContext.h

@@ -0,0 +1,65 @@
+// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
+// SPDX-FileCopyrightText: 2023 Jorrit Rouwe
+// SPDX-License-Identifier: MIT
+
+#pragma once
+
+#include <Jolt/Core/NonCopyable.h>
+#include <Jolt/Physics/Body/MotionProperties.h>
+
+JPH_NAMESPACE_BEGIN
+
+class Body;
+class SoftBodyMotionProperties;
+
+/// Temporary data used by the update of a soft body
+class SoftBodyUpdateContext : public NonCopyable
+{
+public:
+	static constexpr uint				cVertexCollisionBatch = 64;					///< Number of vertices to process in a batch in DetermineCollisionPlanes
+	static constexpr uint				cEdgeConstraintBatch = 256;					///< Number of edge constraints to process in a batch in ApplyEdgeConstraints
+
+	// Input
+	Body *								mBody;										///< Body that is being updated
+	SoftBodyMotionProperties *			mMotionProperties;							///< Motion properties of that body
+	RMat44								mCenterOfMassTransform;						///< Transform of the body relative to the soft body
+	Vec3								mGravity;									///< Gravity vector in local space of the soft body
+	Vec3								mDisplacementDueToGravity;					///< Displacement of the center of mass due to gravity in the current time step
+	float								mDeltaTime;									///< Delta time for the current time step
+	float								mSubStepDeltaTime;							///< Delta time for each sub step
+
+	/// Describes progress in the current update
+	enum class EState
+	{
+		DetermineCollisionPlanes,													///< Determine collision planes for vertices in parallel
+		ApplyEdgeConstraints,														///< Apply edge constraints in parallel
+		Done																		///< Update is finished
+	};
+
+	/// Construct the edge constraint iterator starting at a new group
+	static inline uint64				sGetEdgeGroupStart(uint32 inGroup)
+	{
+		return uint64(inGroup) << 32;
+	}
+
+	/// Get the group and start index from the edge constraint iterator
+	static inline void					sGetEdgeGroupAndStartIdx(uint64 inNextEdgeConstraint, uint32 &outGroup, uint32 &outStartIdx)
+	{
+		outGroup = uint32(inNextEdgeConstraint >> 32);
+		outStartIdx = uint32(inNextEdgeConstraint);
+	}
+
+	// State of the update
+	atomic<EState>						mState { EState::DetermineCollisionPlanes };///< Current state of the update
+	atomic<uint>						mNextCollisionVertex { 0 };					///< Next vertex to process for DetermineCollisionPlanes
+	atomic<uint>						mNumCollisionVerticesProcessed { 0 };		///< Number of vertices processed by DetermineCollisionPlanes, used to determine if we can start simulating
+	atomic<uint>						mNextIteration { 0 };						///< Next simulation iteration to process
+	atomic<uint64>						mNextEdgeConstraint { 0 };					///< Next edge constraint group and start index to process
+	atomic<uint>						mNumEdgeConstraintsProcessed { 0 };			///< Number of edge constraints processed by ApplyEdgeConstraints, used to determine if we can go to the next group / iteration
+
+	// Output
+	Vec3								mDeltaPosition;								///< Delta position of the body in the current time step, should be applied after the update
+	ECanSleep							mCanSleep;									///< Can the body sleep? Should be applied after the update
+};
+
+JPH_NAMESPACE_END

+ 0 - 1
Jolt/Physics/SoftBody/SoftBodyVertex.h

@@ -22,7 +22,6 @@ public:
 	int				mCollidingShapeIndex;				///< Index in the colliding shapes list of the body we may collide with
 	float			mLargestPenetration;				///< Used while finding the collision plane, stores the largest penetration found so far
 	float			mInvMass;							///< Inverse mass (1 / mass)
-	float			mProjectedDistance;					///< Distance along the normal of the collision plane along which the particle was moved to resolve the collision
 };
 
 JPH_NAMESPACE_END

+ 17 - 18
Samples/SamplesApp.cpp

@@ -1408,29 +1408,28 @@ bool SamplesApp::CastProbe(float inProbeLength, float &outFraction, RVec3 &outPo
 			// Create a soft body vertex
 			const float fraction = 0.2f;
 			const float max_distance = 10.0f;
-			SoftBodyVertex tmp_vertex;
-			tmp_vertex.mInvMass = 1.0f;
-			tmp_vertex.mPosition = fraction * direction;
-			tmp_vertex.mVelocity = 10.0f * direction;
-			tmp_vertex.mCollidingShapeIndex = -1;
-			tmp_vertex.mLargestPenetration = -FLT_MAX;
-			Array<SoftBodyVertex> vertices = { tmp_vertex };
+			SoftBodyVertex vertex;
+			vertex.mInvMass = 1.0f;
+			vertex.mPosition = fraction * direction;
+			vertex.mVelocity = 10.0f * direction;
+			vertex.mCollidingShapeIndex = -1;
+			vertex.mLargestPenetration = -FLT_MAX;
 
 			// Get shapes in a large radius around the start position
-			AABox box(Vec3(start + vertices[0].mPosition), max_distance);
+			AABox box(Vec3(start + vertex.mPosition), max_distance);
 			AllHitCollisionCollector<TransformedShapeCollector> collector;
 			mPhysicsSystem->GetNarrowPhaseQuery().CollectTransformedShapes(box, collector);
 
 			// Closest point found using CollideShape, position relative to 'start'
-			Vec3 closest_point = vertices[0].mPosition;
+			Vec3 closest_point = vertex.mPosition;
 			float closest_point_penetration = 0;
 
 			// Test against each shape
 			for (const TransformedShape &ts : collector.mHits)
 			{
 				int colliding_shape_index = int(&ts - collector.mHits.data());
-				ts.mShape->CollideSoftBodyVertices((RMat44::sTranslation(-start) * ts.GetCenterOfMassTransform()).ToMat44(), ts.GetShapeScale(), vertices, 1.0f / 60.0f, Vec3::sZero(), colliding_shape_index);
-				if (vertices[0].mCollidingShapeIndex == colliding_shape_index)
+				ts.mShape->CollideSoftBodyVertices((RMat44::sTranslation(-start) * ts.GetCenterOfMassTransform()).ToMat44(), ts.GetShapeScale(), &vertex, 1, 1.0f / 60.0f, Vec3::sZero(), colliding_shape_index);
+				if (vertex.mCollidingShapeIndex == colliding_shape_index)
 				{
 					// To draw a plane, we need a point but CollideSoftBodyVertices doesn't provide one, so we use CollideShape with a tiny sphere to get the closest point and then project that onto the plane to draw the plane
 					SphereShape point_sphere(1.0e-6f);
@@ -1438,7 +1437,7 @@ bool SamplesApp::CastProbe(float inProbeLength, float &outFraction, RVec3 &outPo
 					CollideShapeSettings settings;
 					settings.mMaxSeparationDistance = max_distance;
 					ClosestHitCollisionCollector<CollideShapeCollector> collide_shape_collector;
-					ts.CollideShape(&point_sphere, Vec3::sReplicate(1.0f), RMat44::sTranslation(start + vertices[0].mPosition), settings, start, collide_shape_collector);
+					ts.CollideShape(&point_sphere, Vec3::sReplicate(1.0f), RMat44::sTranslation(start + vertex.mPosition), settings, start, collide_shape_collector);
 					if (collide_shape_collector.HadHit())
 					{
 						closest_point = collide_shape_collector.mHit.mContactPointOn2;
@@ -1448,17 +1447,17 @@ bool SamplesApp::CastProbe(float inProbeLength, float &outFraction, RVec3 &outPo
 			}
 
 			// Draw test point
-			mDebugRenderer->DrawMarker(start + vertices[0].mPosition, Color::sYellow, 0.1f);
+			mDebugRenderer->DrawMarker(start + vertex.mPosition, Color::sYellow, 0.1f);
 			mDebugRenderer->DrawMarker(start + closest_point, Color::sRed, 0.1f);
 
 			// Draw collision plane
-			if (vertices[0].mCollidingShapeIndex != -1)
+			if (vertex.mCollidingShapeIndex != -1)
 			{
-				RVec3 plane_point = start + closest_point - vertices[0].mCollisionPlane.GetNormal() * vertices[0].mCollisionPlane.SignedDistance(closest_point);
-				mDebugRenderer->DrawPlane(plane_point, vertices[0].mCollisionPlane.GetNormal(), Color::sGreen, 2.0f);
+				RVec3 plane_point = start + closest_point - vertex.mCollisionPlane.GetNormal() * vertex.mCollisionPlane.SignedDistance(closest_point);
+				mDebugRenderer->DrawPlane(plane_point, vertex.mCollisionPlane.GetNormal(), Color::sGreen, 2.0f);
 
-				if (abs(closest_point_penetration - vertices[0].mLargestPenetration) > 0.001f)
-					mDebugRenderer->DrawText3D(plane_point, StringFormat("Pen %f (exp %f)", (double)vertices[0].mLargestPenetration, (double)closest_point_penetration));
+				if (abs(closest_point_penetration - vertex.mLargestPenetration) > 0.001f)
+					mDebugRenderer->DrawText3D(plane_point, StringFormat("Pen %f (exp %f)", (double)vertex.mLargestPenetration, (double)closest_point_penetration));
 			}
 		}
 		break;

+ 12 - 0
Samples/Utils/SoftBodyCreator.cpp

@@ -81,6 +81,10 @@ Ref<SoftBodySharedSettings> CreateCloth(uint inGridSize, float inGridSpacing, bo
 			settings->AddFace(f);
 		}
 
+	// Optimize the settings
+	SoftBodySharedSettings::OptimizationResults results;
+	settings->Optimize(results);
+
 	return settings;
 }
 
@@ -221,6 +225,10 @@ Ref<SoftBodySharedSettings> CreateCube(uint inGridSize, float inGridSpacing)
 			settings->AddFace(f);
 		}
 
+	// Optimize the settings
+	SoftBodySharedSettings::OptimizationResults results;
+	settings->Optimize(results);
+
 	return settings;
 }
 
@@ -303,6 +311,10 @@ Ref<SoftBodySharedSettings> CreateSphere(float inRadius, uint inNumTheta, uint i
 		settings->AddFace(f);
 	}
 
+	// Optimize the settings
+	SoftBodySharedSettings::OptimizationResults results;
+	settings->Optimize(results);
+
 	return settings;
 }
 

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä