diff --git a/.github/release.yml b/.github/release.yml
new file mode 100644
index 000000000..0f7212fd7
--- /dev/null
+++ b/.github/release.yml
@@ -0,0 +1,34 @@
+# .github/release.yml
+
+changelog:
+ exclude:
+ labels:
+ - ignore-for-release
+ authors:
+ - dependabot
+ categories:
+ - title: 💥 Breaking Changes
+ labels:
+ - breaking-change
+ - title: 🎉 New Features
+ labels:
+ - enhancement
+ - new-feature
+ - title: 🚀 Performance
+ labels:
+ - performance
+ - title: 🧠Fiddling
+ labels:
+ - fiddling
+ - title: 🎮 Demos App
+ labels:
+ - demos
+ - title: 🪲 Bug Fixes
+ labels:
+ - bug
+ - title: 📄 Documentation
+ labels:
+ - documentation
+ - title: 💪 Other Changes
+ labels:
+ - "*"
\ No newline at end of file
diff --git a/.github/workflows/bepu-docs-github.yml b/.github/workflows/bepu-docs-github.yml
new file mode 100644
index 000000000..d242b50b2
--- /dev/null
+++ b/.github/workflows/bepu-docs-github.yml
@@ -0,0 +1,45 @@
+# More GitHub Actions for Azure: https://github.com/Azure/actions
+
+name: Build Bepu Docs for GitHub Pages
+
+env:
+ COMMON_SETTINGS_PATH: Documentation/docfx.json
+
+on:
+ workflow_dispatch:
+
+jobs:
+ publish-docs:
+ runs-on: windows-latest
+
+ steps:
+ - name: .NET SDK Setup
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 9.x
+
+ - name: Checkout Bepu
+ uses: actions/checkout@v4
+
+ - name: Install DocFX
+ # This installs the latest version of DocFX and may introduce breaking changes
+ # run: dotnet tool update -g docfx
+ # This installs a specific, tested version of DocFX.
+ run: dotnet tool update -g docfx --version 2.78.2
+
+ - name: Build Bepu API Docs
+ run: docfx metadata ${{ env.COMMON_SETTINGS_PATH }}
+
+ - name: Build Bepu Docs
+ run: docfx build ${{ env.COMMON_SETTINGS_PATH }}
+
+ - name: Fix Documentation Links
+ run: powershell -File Documentation/fix-links.ps1 -sitePath "Documentation/_site" -repoUrl "https://github.com/bepu/bepuphysics2/blob/master"
+
+ - name: Deploy
+ uses: peaceiris/actions-gh-pages@v4.0.0
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: Documentation/_site
+ publish_branch: gh-pages
+ cname: docs.bepuphysics.com
\ No newline at end of file
diff --git a/.github/workflows/dotnet-core-publish.yml b/.github/workflows/dotnet-core-publish.yml
new file mode 100644
index 000000000..2225e8514
--- /dev/null
+++ b/.github/workflows/dotnet-core-publish.yml
@@ -0,0 +1,56 @@
+name: .NET Core - Publish NuGet Packages
+
+env:
+ COMMON_SETTINGS_PATH: CommonSettings.props
+ BASE_RUN_NUMBER: 23
+
+on: [workflow_dispatch]
+
+jobs:
+ build:
+
+ runs-on: windows-latest
+
+ steps:
+ - name: Print run_number
+ run: echo ${{ github.run_number }}
+ - name: Set version number
+ run: |
+ $version = "2.5.0-beta.$(${{ github.run_number }} + $env:BASE_RUN_NUMBER)"
+ echo "VERSION=$version" >> $env:GITHUB_ENV
+ shell: powershell
+ - name: Print VERSION
+ run: echo "VERSION is $env:VERSION"
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+ include-prerelease: true
+ - name: Set Version in CommonSettings.props
+ run: |
+ $settingsContent = Get-Content -Path ${{ env.COMMON_SETTINGS_PATH }} -Raw
+ $updatedContent = $settingsContent -replace '.*?', "${{ env.VERSION }}"
+ Set-Content -Path ${{ env.COMMON_SETTINGS_PATH }} -Value $updatedContent
+ - name: Install dependencies
+ run: |
+ dotnet restore DemoContentBuilder
+ dotnet restore Demos
+ - name: Build
+ run: |
+ dotnet build DemoContentBuilder --configuration Release --no-restore /p:Platform=x64
+ dotnet build Demos --configuration Release --no-restore
+ - name: Test
+ run: dotnet test DemoTests -c Release --verbosity normal
+ - name: Publish
+ if: github.event_name != 'pull_request'
+ run: |
+ dotnet nuget add source "https://nuget.pkg.github.com/bepu/index.json" --name "github" --username "rossnordby" --password "${{secrets.GITHUB_TOKEN}}"
+ dotnet pack "BepuPhysics" -c Release
+ dotnet pack "BepuUtilities" -c Release
+ dotnet nuget push "**/*.nupkg" -s "github" -k "${{secrets.GITHUB_TOKEN}}" --skip-duplicate
+ dotnet nuget push "**/*.nupkg" -s "https://api.nuget.org/v3/index.json" -k "${{secrets.NUGET_KEY}}" --skip-duplicate
+ - name: Create GitHub Release Draft
+ run: |
+ gh release create ${{ env.VERSION }} --title "v${{ env.VERSION }}" --notes "Release notes for ${{ env.VERSION }}" --draft
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml
index 3bd6cdd6b..4c4bce2ff 100644
--- a/.github/workflows/dotnet-core.yml
+++ b/.github/workflows/dotnet-core.yml
@@ -1,10 +1,14 @@
-name: .NET Core
+name: .NET Core - Build and Test
on:
push:
- branches: [ master ]
+ paths-ignore:
+ - '.github/**'
+ - 'Documentation/**'
pull_request:
- branches: [ master ]
+ paths-ignore:
+ - '.github/**'
+ - 'Documentation/**'
jobs:
build:
@@ -12,7 +16,11 @@ jobs:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.x'
+ include-prerelease: true
- name: Install dependencies
run: |
dotnet restore DemoContentBuilder
@@ -23,10 +31,11 @@ jobs:
dotnet build Demos --configuration Release --no-restore
- name: Test
run: dotnet test DemoTests -c Release --verbosity normal
- - name: Publish
- run: |
- dotnet nuget add source "https://nuget.pkg.github.com/bepu/index.json" --name "github" --username "rossnordby" --password "${{secrets.GITHUB_TOKEN}}"
- dotnet pack "BepuPhysics" -c Release
- dotnet pack "BepuUtilities" -c Release
- dotnet nuget push "**/*.nupkg" -s "github" -k "${{secrets.GITHUB_TOKEN}}" --skip-duplicate
- dotnet nuget push "**/*.nupkg" -s "https://api.nuget.org/v3/index.json" -k "${{secrets.NUGET_KEY}}" --skip-duplicate
\ No newline at end of file
+# - name: Publish
+# if: github.event_name != 'pull_request'
+# run: |
+# dotnet nuget add source "https://nuget.pkg.github.com/bepu/index.json" --name "github" --username "rossnordby" --password "${{secrets.GITHUB_TOKEN}}"
+# dotnet pack "BepuPhysics" -c Release
+# dotnet pack "BepuUtilities" -c Release
+# dotnet nuget push "**/*.nupkg" -s "github" -k "${{secrets.GITHUB_TOKEN}}" --skip-duplicate
+# dotnet nuget push "**/*.nupkg" -s "https://api.nuget.org/v3/index.json" -k "${{secrets.NUGET_KEY}}" --skip-duplicate
diff --git a/.gitignore b/.gitignore
index 1710e230b..266ca1f2e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -205,3 +205,10 @@ ModelManifest.xml
# Don't ignore content files that happen to use the .obj extension.
!Demos/Content/*.obj
+
+# JetBrains Rider
+.idea/
+
+# DocFX API Generated Pages
+Documentation/api/*
+Documentation/_site/*
\ No newline at end of file
diff --git a/BepuPhysics/BatchCompressor.cs b/BepuPhysics/BatchCompressor.cs
index 638b554a1..e622ec56e 100644
--- a/BepuPhysics/BatchCompressor.cs
+++ b/BepuPhysics/BatchCompressor.cs
@@ -1,9 +1,9 @@
-using BepuUtilities;
+using BepuPhysics.Constraints;
+using BepuUtilities;
using BepuUtilities.Collections;
using BepuUtilities.Memory;
using System;
using System.Diagnostics;
-using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
@@ -12,7 +12,7 @@ namespace BepuPhysics
///
/// Handles the movement of constraints from higher indexed batches into lower indexed batches to avoid accumulating a bunch of unnecessary ConstraintBatches.
///
- public class BatchCompressor
+ public unsafe class BatchCompressor
{
//We want to keep removes as fast as possible. So, when removing constraints, no attempt is made to pull constraints from higher constraint batches into the revealed slot.
//Over time, this could result in lots of extra constraint batches that ruin multithreading performance.
@@ -97,7 +97,7 @@ struct AnalysisRegion
Action analysisWorkerDelegate;
- public BatchCompressor(Solver solver, Bodies bodies, float targetCandidateFraction = 0.01f, float maximumCompressionFraction = 0.0005f)
+ public BatchCompressor(Solver solver, Bodies bodies, float targetCandidateFraction = 0.005f, float maximumCompressionFraction = 0.0005f)
{
this.Solver = solver;
this.Bodies = bodies;
@@ -107,13 +107,12 @@ public BatchCompressor(Solver solver, Bodies bodies, float targetCandidateFracti
}
-
void AnalysisWorker(int workerIndex)
{
int jobIndex;
while ((jobIndex = Interlocked.Increment(ref analysisJobIndex)) < analysisJobs.Count)
{
- DoJob(ref analysisJobs[jobIndex], workerIndex, threadDispatcher.GetThreadMemoryPool(workerIndex));
+ DoJob(ref analysisJobs[jobIndex], workerIndex, threadDispatcher.WorkerPools[workerIndex]);
}
}
@@ -124,7 +123,27 @@ void AnalysisWorker(int workerIndex)
//It'll just require some testing.
//(The broad phase is a pretty likely candidate for this overlay- it both causes no changes in constraints and is very stally compared to most other phases.)
- unsafe void DoJob(ref AnalysisRegion region, int workerIndex, BufferPool pool)
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void TryToFindBetterBatchForConstraint(
+ BufferPool pool, ref QuickList compressions, ref TypeBatch typeBatch, int* bodyHandles, ref ActiveConstraintDynamicBodyHandleCollector handleAccumulator, TypeProcessor typeProcessor, int constraintIndex)
+ {
+ handleAccumulator.Count = 0;
+ Solver.EnumerateConnectedRawBodyReferences(ref typeBatch, constraintIndex, ref handleAccumulator);
+ var dynamicBodyHandles = new Span(bodyHandles, handleAccumulator.Count);
+ for (int batchIndex = nextBatchIndex - 1; batchIndex >= 0; --batchIndex)
+ {
+ if (Solver.batchReferencedHandles[batchIndex].CanFit(dynamicBodyHandles))
+ {
+ compressions.Add(new Compression { ConstraintHandle = typeBatch.IndexToHandle[constraintIndex], TargetBatch = batchIndex }, pool);
+ return;
+ }
+ }
+
+ }
+
+
+ void DoJob(ref AnalysisRegion region, int workerIndex, BufferPool pool)
{
ref var compressions = ref this.workerCompressions[workerIndex];
ref var batch = ref Solver.ActiveSet.Batches[nextBatchIndex];
@@ -135,27 +154,31 @@ unsafe void DoJob(ref AnalysisRegion region, int workerIndex, BufferPool pool)
//Each job only works on a subset of a single type batch.
var bodiesPerConstraint = typeProcessor.BodiesPerConstraint;
var bodyHandles = stackalloc int[bodiesPerConstraint];
- ActiveConstraintBodyHandleCollector handleAccumulator;
+ ActiveConstraintDynamicBodyHandleCollector handleAccumulator;
handleAccumulator.Bodies = Bodies;
handleAccumulator.Handles = bodyHandles;
- var bodyHandlesSpan = new Span(bodyHandles, bodiesPerConstraint);
- for (int i = region.StartIndexInTypeBatch; i < region.EndIndexInTypeBatch; ++i)
+ handleAccumulator.Count = 0;
+ if (nextBatchIndex == Solver.FallbackBatchThreshold)
{
- //Check if this constraint can be removed.
- handleAccumulator.Index = 0;
- typeProcessor.EnumerateConnectedBodyIndices(ref typeBatch, i, ref handleAccumulator);
- for (int batchIndex = 0; batchIndex < nextBatchIndex; ++batchIndex)
+ for (int i = region.StartIndexInTypeBatch; i < region.EndIndexInTypeBatch; ++i)
{
- //The batch index will never be the fallback batch, since the fallback batch is the very last batch (if it exists at all). So uses batch referenced handles is safe.
- if (Solver.batchReferencedHandles[batchIndex].CanFit(bodyHandlesSpan))
- {
- compressions.Add(new Compression { ConstraintHandle = typeBatch.IndexToHandle[i], TargetBatch = batchIndex }, pool);
- break;
- }
+ //This is a fallback batch; the rules are a little different.
+ //Not all constraint slots up to the typeBatch.ConstraintCount are guaranteed to actually exist. It's potentially sparse.
+ //Just skip them.
+ if (typeBatch.IndexToHandle[i].Value >= 0)
+ TryToFindBetterBatchForConstraint(pool, ref compressions, ref typeBatch, bodyHandles, ref handleAccumulator, typeProcessor, i);
+ }
+ }
+ else
+ {
+ for (int i = region.StartIndexInTypeBatch; i < region.EndIndexInTypeBatch; ++i)
+ {
+ TryToFindBetterBatchForConstraint(pool, ref compressions, ref typeBatch, bodyHandles, ref handleAccumulator, typeProcessor, i);
}
}
}
+
struct CompressionTarget
{
public ushort WorkerIndex;
@@ -172,23 +195,22 @@ public int Compare(ref CompressionTarget a, ref CompressionTarget b)
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private unsafe void ApplyCompression(int sourceBatchIndex, ref ConstraintBatch sourceBatch, ref Compression compression)
+ private void ApplyCompression(int sourceBatchIndex, ref ConstraintBatch sourceBatch, ref Compression compression)
{
- //Careful here: this is a reference for the sake of not doing pointless copies, but you cannot rely on it having the same values after the completion of the transfer.
- ref var constraintLocation = ref Solver.HandleToConstraint[compression.ConstraintHandle.Value];
+ var constraintLocation = Solver.HandleToConstraint[compression.ConstraintHandle.Value];
var typeProcessor = Solver.TypeProcessors[constraintLocation.TypeId];
if (sourceBatchIndex == Solver.FallbackBatchThreshold)
{
//We're optimizing the fallback batch, so we need to be careful about compressions interfering with each other. The parallel analysis assumed each batch
//contained at most one instance of each body, which doesn't hold for the fallback batch.
//Easy enough to address: check to see if the target batch can still hold the constraint.
- var bodyHandles = stackalloc int[typeProcessor.BodiesPerConstraint];
- ActiveConstraintBodyHandleCollector handleAccumulator;
+ var dynamicBodyHandles = stackalloc int[typeProcessor.BodiesPerConstraint];
+ ActiveConstraintDynamicBodyHandleCollector handleAccumulator;
handleAccumulator.Bodies = Bodies;
- handleAccumulator.Handles = bodyHandles;
- handleAccumulator.Index = 0;
- Solver.EnumerateConnectedBodies(compression.ConstraintHandle, ref handleAccumulator);
- if (!Solver.batchReferencedHandles[compression.TargetBatch].CanFit(new Span(bodyHandles, typeProcessor.BodiesPerConstraint)))
+ handleAccumulator.Handles = dynamicBodyHandles;
+ handleAccumulator.Count = 0;
+ Solver.EnumerateConnectedRawBodyReferences(compression.ConstraintHandle, ref handleAccumulator);
+ if (!Solver.batchReferencedHandles[compression.TargetBatch].CanFit(new Span(dynamicBodyHandles, handleAccumulator.Count)))
{
//Another compression from the fallback batch has blocked this compression.
//Note that this isn't really a problem- batch compression is an incremental process. If some other compression was possible, a future frame will find it pretty quickly.
@@ -222,7 +244,7 @@ public void Compress(BufferPool pool, IThreadDispatcher threadDispatcher = null,
for (int i = 0; i < workerCount; ++i)
{
//Be careful: the jobs may require resizes on the compression count list. That requires the use of per-worker pools.
- workerCompressions[i] = new QuickList(Math.Max(8, maximumCompressionCount), threadDispatcher == null ? pool : threadDispatcher.GetThreadMemoryPool(i));
+ workerCompressions[i] = new QuickList(Math.Max(8, maximumCompressionCount), threadDispatcher == null ? pool : threadDispatcher.WorkerPools[i]);
}
//In any given compression attempt, we only optimize over one ConstraintBatch.
@@ -292,7 +314,7 @@ public void Compress(BufferPool pool, IThreadDispatcher threadDispatcher = null,
{
analysisJobIndex = -1;
this.threadDispatcher = threadDispatcher;
- threadDispatcher.DispatchWorkers(analysisWorkerDelegate);
+ threadDispatcher.DispatchWorkers(analysisWorkerDelegate, analysisJobs.Count);
this.threadDispatcher = null;
}
else
@@ -377,11 +399,16 @@ public void Compress(BufferPool pool, IThreadDispatcher threadDispatcher = null,
//var applyTime = 1e6 * (applyEnd - applyStart) / Stopwatch.Frequency;
//Console.WriteLine($"Apply time (us): {applyTime}, per applied: {applyTime / compressionsApplied}, (maximum: {maximumCompressionCount})");
- for (int i = 0; i < workerCount; ++i)
+ if (threadDispatcher == null)
+ workerCompressions[0].Dispose(pool);
+ else
{
- //Be careful: the jobs may require resizes on the compression count list. That requires the use of per-worker pools.
- workerCompressions[i].Dispose((threadDispatcher == null ? pool : threadDispatcher.GetThreadMemoryPool(i)));
+ for (int i = 0; i < workerCount; ++i)
+ {
+ workerCompressions[i].Dispose(threadDispatcher.WorkerPools[i]);
+ }
}
+
pool.Return(ref workerCompressions);
}
diff --git a/BepuPhysics/BepuPhysics.csproj b/BepuPhysics/BepuPhysics.csproj
index 35d575690..6d9803f7b 100644
--- a/BepuPhysics/BepuPhysics.csproj
+++ b/BepuPhysics/BepuPhysics.csproj
@@ -1,33 +1,24 @@
-
+
- net5.0
- 2.4.0-beta6
- Bepu Entertainment LLC
- Ross Nordby
Speedy real time physics simulation library.
- © Bepu Entertainment LLC
- https://github.com/bepu/bepuphysics2
- Apache-2.0
- https://github.com/bepu/bepuphysics2
- bepuphysicslogo256.png
Debug;Release;ReleaseNoProfiling
- latest
physics;3d;rigid body;real time;simulation
- True
-
- true
- false
key.snk
+
+
+
+
+ 1573;1591;CA2014
-
+
+
+
false
TRACE;DEBUG;CHECKMATH;PROFILE
- true
- embedded
true
TRACE;RELEASE;PROFILE
@@ -76,10 +67,6 @@
TextTemplatingFileGenerator
ContactNonconvexTypes.cs
-
- True
-
-
\ No newline at end of file
diff --git a/BepuPhysics/Bodies.cs b/BepuPhysics/Bodies.cs
index b2de36660..fe0148ed4 100644
--- a/BepuPhysics/Bodies.cs
+++ b/BepuPhysics/Bodies.cs
@@ -3,22 +3,25 @@
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
-using BepuPhysics.Constraints;
using BepuPhysics.Collidables;
using BepuPhysics.CollisionDetection;
using BepuUtilities;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
namespace BepuPhysics
{
+ ///
+ /// Location of a body in memory.
+ ///
public struct BodyMemoryLocation
{
///
- /// Index of the set owning the body reference. If the island index is 0, the body is active.
+ /// Index of the set owning the body reference. If the set index is 0, the body is awake. If the set index is greater than zero, the body is asleep.
///
public int SetIndex;
///
- /// Index of the body within its owning set. If the body is active (and so the Island index is -1), this is an index into the Bodies data arrays.
- /// If it is nonnegative, it is an index into the inactive island
+ /// Index of the body within its owning set.
///
public int Index;
}
@@ -26,13 +29,16 @@ public struct BodyMemoryLocation
///
/// Collection of all allocated bodies.
///
- public class Bodies
+ public partial class Bodies
{
///
/// Remaps a body handle integer value to the actual array index of the body.
/// The backing array index may change in response to cache optimization.
///
public Buffer HandleToLocation;
+ ///
+ /// Pool from which handles are pulled for new bodies.
+ ///
public IdPool HandlePool;
///
/// The set of existing bodies. The slot at index 0 contains all active bodies. Later slots, if allocated, contain the bodies associated with inactive islands.
@@ -45,13 +51,24 @@ public class Bodies
/// Reference to the active body set.
public unsafe ref BodySet ActiveSet { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return ref *Sets.Memory; } }
- //TODO: Having Inertias publicly exposed seems like a recipe for confusion, given its ephemeral nature. We may want to explicitly delete it after frame execution and
- //never expose it. If the user really wants an up to date world space inertia, it's pretty easy for them to build it from the local inertia and orientation anyway.
///
- /// The world transformed inertias of active bodies as of the last update. Note that this is not automatically updated for direct orientation changes or for body memory moves.
- /// It is only updated once during the frame. It should be treated as ephemeral information.
+ /// Gets a reference to a body in the collection.
+ ///
+ /// Handle of the body to pull a reference for.
+ /// Reference to the requested body.
+ /// This is an alias for the older and the constructor. They are all equivalent.
+ public BodyReference this[BodyHandle handle]
+ {
+ get
+ {
+ return new BodyReference(handle, this);
+ }
+ }
+
+
+ ///
+ /// Gets the pool used by the bodies collection to allocate and free memory.
///
- public Buffer Inertias;
public BufferPool Pool { get; private set; }
internal IslandAwakener awakener;
@@ -75,7 +92,7 @@ public class Bodies
/// Initial number of bodies to allocate space for in the active set.
/// Initial number of islands to allocate space for in the Sets buffer.
/// Expected number of constraint references per body to allocate space for.
- public unsafe Bodies(BufferPool pool, Shapes shapes, BroadPhase broadPhase,
+ public Bodies(BufferPool pool, Shapes shapes, BroadPhase broadPhase,
int initialBodyCapacity, int initialIslandCapacity, int initialConstraintCapacityPerBody)
{
this.Pool = pool;
@@ -113,7 +130,7 @@ public void UpdateBounds(BodyHandle bodyHandle)
ref var collidable = ref set.Collidables[location.Index];
if (collidable.Shape.Exists)
{
- shapes.UpdateBounds(set.Poses[location.Index], ref collidable.Shape, out var bodyBounds);
+ shapes.UpdateBounds(set.DynamicsState[location.Index].Motion.Pose, collidable.Shape, out var bodyBounds);
if (location.SetIndex == 0)
{
broadPhase.UpdateActiveBounds(collidable.BroadPhaseIndex, bodyBounds.Min, bodyBounds.Max);
@@ -133,7 +150,7 @@ void AddCollidableToBroadPhase(BodyHandle bodyHandle, in RigidPose pose, in Body
//Note that we have to calculate an initial bounding box for the broad phase to be able to insert it efficiently.
//(In the event of batch adds, you'll want to use batched AABB calculations or just use cached values.)
//Note: the min and max here are in absolute coordinates, which means this is a spot that has to be updated in the event that positions use a higher precision representation.
- shapes.UpdateBounds(pose, ref collidable.Shape, out var bodyBounds);
+ shapes.UpdateBounds(pose, collidable.Shape, out var bodyBounds);
//Note that new body collidables are always assumed to be active.
collidable.BroadPhaseIndex =
broadPhase.AddActive(
@@ -163,7 +180,7 @@ void RemoveCollidableFromBroadPhase(ref Collidable collidable)
///
/// Description of the body to add.
/// Handle of the created body.
- public unsafe BodyHandle Add(in BodyDescription description)
+ public BodyHandle Add(in BodyDescription description)
{
Debug.Assert(HandleToLocation.Allocated, "The backing memory of the bodies set should be initialized before use.");
var handleIndex = HandlePool.Take();
@@ -186,6 +203,11 @@ public unsafe BodyHandle Add(in BodyDescription description)
{
AddCollidableToBroadPhase(handle, description.Pose, description.LocalInertia, ref ActiveSet.Collidables[index]);
}
+ else
+ {
+ //Don't want to leak undefined data into the collidable state if there's no shape.
+ ActiveSet.Collidables[index].BroadPhaseIndex = -1;
+ }
return handle;
}
@@ -265,9 +287,47 @@ internal void AddConstraint(int bodyIndex, ConstraintHandle constraintHandle, in
///
/// Index of the active body.
/// Handle of the constraint to remove.
- internal void RemoveConstraintReference(int bodyIndex, ConstraintHandle constraintHandle)
+ /// True if the number of constraints remaining attached to the body is 0, false otherwise.
+ internal bool RemoveConstraintReference(int bodyIndex, ConstraintHandle constraintHandle)
+ {
+ return ActiveSet.RemoveConstraintReference(bodyIndex, constraintHandle, MinimumConstraintCapacityPerBody, Pool);
+ }
+
+ ///
+ /// Gets whether the inertia matches that of a kinematic body (that is, all inverse mass and inertia components are zero).
+ ///
+ /// Body inertia to analyze.
+ /// True if all components of inverse mass and inertia are zero, false otherwise.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static bool IsKinematic(BodyInertia inertia)
+ {
+ return IsKinematic(&inertia);
+ }
+
+ ///
+ /// Gets whether the inertia matches that of a kinematic body (that is, all inverse mass and inertia components are zero).
+ ///
+ /// Body inertia to analyze. Must be a reference to fixed data; a pointer will be taken.
+ /// True if all components of inverse mass and inertia are zero, false otherwise.
+ /// This is not exposed by default because of the risk of a non-obvious GC hole.
+ /// It exists because it's a mildly more convenient form than the pointer overload, and every use within the engine references only pinned data.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal unsafe static bool IsKinematicUnsafeGCHole(ref BodyInertia inertia)
{
- ActiveSet.RemoveConstraintReference(bodyIndex, constraintHandle, MinimumConstraintCapacityPerBody, Pool);
+ return IsKinematic((BodyInertia*)Unsafe.AsPointer(ref inertia));
+ }
+
+ ///
+ /// Checks inertia lanes for kinematicity (all inverse mass and inertia values are zero).
+ ///
+ /// Inertia to examine for kinematicity.
+ /// Mask of lanes which contain zeroed inverse masses and inertias.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector IsKinematic(BodyInertiaWide inertia)
+ {
+ return Vector.Equals(Vector.BitwiseOr(
+ Vector.BitwiseOr(Vector.BitwiseOr(inertia.InverseMass, inertia.InverseInertiaTensor.XX), Vector.BitwiseOr(inertia.InverseInertiaTensor.YX, inertia.InverseInertiaTensor.YY)),
+ Vector.BitwiseOr(Vector.BitwiseOr(inertia.InverseInertiaTensor.ZX, inertia.InverseInertiaTensor.ZY), inertia.InverseInertiaTensor.ZZ)), Vector.Zero);
}
///
@@ -276,9 +336,18 @@ internal void RemoveConstraintReference(int bodyIndex, ConstraintHandle constrai
/// Body inertia to analyze.
/// True if all components of inverse mass and inertia are zero, false otherwise.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool IsKinematic(in BodyInertia inertia)
+ public unsafe static bool IsKinematic(BodyInertia* inertia)
{
- return inertia.InverseMass == 0 && HasLockedInertia(inertia.InverseInertiaTensor);
+ if (Avx.IsSupported)
+ {
+ var inertiaVector = Avx.LoadVector256((float*)inertia);
+ var masked = Avx.CompareEqual(inertiaVector, Vector256.Zero);
+ return (Avx.MoveMask(masked) & 0x7F) == 0x7F;
+ }
+ else
+ {
+ return inertia->InverseMass == 0 && HasLockedInertia(&inertia->InverseInertiaTensor);
+ }
}
///
@@ -287,62 +356,64 @@ public static bool IsKinematic(in BodyInertia inertia)
/// Body inertia to analyze.
/// True if all components of inverse mass and inertia are zero, false otherwise.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static bool HasLockedInertia(in Symmetric3x3 inertia)
+ public unsafe static bool HasLockedInertia(Symmetric3x3 inertia)
{
- return inertia.XX == 0 &&
- inertia.YX == 0 &&
- inertia.YY == 0 &&
- inertia.ZX == 0 &&
- inertia.ZY == 0 &&
- inertia.ZZ == 0;
+ return HasLockedInertia(&inertia);
}
- private struct ConnectedDynamicCounter : IForEach
+ ///
+ /// Gets whether the angular inertia matches that of a kinematic body (that is, all inverse inertia tensor components are zero).
+ ///
+ /// Body inertia to analyze.
+ /// True if all components of inverse mass and inertia are zero, false otherwise.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe static bool HasLockedInertia(Symmetric3x3* inertia)
{
- public Bodies Bodies;
- public int DynamicCount;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void LoopBody(int bodyIndex)
+ if (Avx.IsSupported)
+ {
+ var inertiaVector = Avx.LoadVector256((float*)inertia);
+ var masked = Avx.CompareEqual(inertiaVector, Vector256.Zero);
+ return (Avx.MoveMask(masked) & 0x3F) == 0x3F;
+ }
+ else
{
- //The solver's connected bodies enumeration directly provides the constraint-stored reference, which is an index in the active set for active constraints and a handle for inactive constraints.
- //We forced the dynamic active at the beginning of BecomeKinematic, so we don't have to worry about the inactive side of things.
- if (!IsKinematic(Bodies.ActiveSet.LocalInertias[bodyIndex]))
- ++DynamicCount;
+ return inertia->XX == 0 &&
+ inertia->YX == 0 &&
+ inertia->YY == 0 &&
+ inertia->ZX == 0 &&
+ inertia->ZY == 0 &&
+ inertia->ZZ == 0;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- void UpdateForKinematicStateChange(BodyHandle handle, ref BodyMemoryLocation location, ref BodySet set, bool newlyKinematic)
+ void UpdateForKinematicStateChange(BodyHandle handle, ref BodyMemoryLocation location, ref BodySet set, bool previouslyKinematic, bool currentlyKinematic)
{
Debug.Assert(location.SetIndex == 0, "If we're changing kinematic state, we should have already awoken the body.");
- ref var collidable = ref set.Collidables[location.Index];
- if (collidable.Shape.Exists)
+ if (previouslyKinematic != currentlyKinematic)
{
- var mobility = IsKinematic(set.LocalInertias[location.Index]) ? CollidableMobility.Kinematic : CollidableMobility.Dynamic;
- if (location.SetIndex == 0)
+ ref var collidable = ref set.Collidables[location.Index];
+ if (collidable.Shape.Exists)
{
- broadPhase.activeLeaves[collidable.BroadPhaseIndex] = new CollidableReference(mobility, handle);
+ //Any collidable references need their encoded mobility updated.
+ var mobility = currentlyKinematic ? CollidableMobility.Kinematic : CollidableMobility.Dynamic;
+ if (location.SetIndex == 0)
+ {
+ broadPhase.ActiveLeaves[collidable.BroadPhaseIndex] = new CollidableReference(mobility, handle);
+ }
+ else
+ {
+ broadPhase.StaticLeaves[collidable.BroadPhaseIndex] = new CollidableReference(mobility, handle);
+ }
}
- else
+ ref var constraints = ref set.Constraints[location.Index];
+ if (currentlyKinematic)
{
- broadPhase.staticLeaves[collidable.BroadPhaseIndex] = new CollidableReference(mobility, handle);
+ solver.UpdateReferencesForBodyBecomingKinematic(handle, location.Index);
}
- }
- if (newlyKinematic)
- {
- ref var constraints = ref set.Constraints[location.Index];
- ConnectedDynamicCounter enumerator;
- enumerator.Bodies = this;
- for (int i = 0; i < constraints.Count; ++i)
+ else
{
- ref var constraint = ref constraints[i];
- enumerator.DynamicCount = 0;
- solver.EnumerateConnectedBodies(constraint.ConnectingConstraintHandle, ref enumerator);
- if (enumerator.DynamicCount == 0)
- {
- //This constraint connects only kinematic bodies; keeping it in the solver would cause a singularity.
- solver.Remove(constraint.ConnectingConstraintHandle);
- }
+ solver.UpdateReferencesForBodyBecomingDynamic(handle, location.Index);
}
}
}
@@ -368,9 +439,16 @@ public void SetLocalInertia(BodyHandle handle, in BodyInertia localInertia)
}
//Note that the HandleToLocation slot reference is still valid; it may have been updated, but handle slots don't move.
ref var set = ref Sets[location.SetIndex];
- var newlyKinematic = IsKinematic(localInertia) && !IsKinematic(set.LocalInertias[location.Index]);
- set.LocalInertias[location.Index] = localInertia;
- UpdateForKinematicStateChange(handle, ref location, ref set, newlyKinematic);
+ ref var inertiaReference = ref set.DynamicsState[location.Index].Inertia;
+ ref var localInertiaReference = ref set.DynamicsState[location.Index].Inertia.Local;
+ var nowKinematic = IsKinematic(localInertia);
+ var previouslyKinematic = IsKinematicUnsafeGCHole(ref inertiaReference.Local);
+ inertiaReference.Local = localInertia;
+ //The world inertia is updated on demand and is not 'persistent' data.
+ //In the event that the body is now kinematic, it won't be updated by pose integration and such, so it should be initialized to zeroes.
+ //Since initializing it to zeroes unconditionally avoids dynamics having some undefined data lingering around in the worst case, might as well.
+ inertiaReference.World = default;
+ UpdateForKinematicStateChange(handle, ref location, ref set, previouslyKinematic, nowKinematic);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -382,7 +460,8 @@ void UpdateForShapeChange(BodyHandle handle, int activeBodyIndex, TypedIndex old
if (newShape.Exists)
{
//Add a collidable to the simulation for the new shape.
- AddCollidableToBroadPhase(handle, set.Poses[activeBodyIndex], set.LocalInertias[activeBodyIndex], ref set.Collidables[activeBodyIndex]);
+ ref var state = ref set.DynamicsState[activeBodyIndex];
+ AddCollidableToBroadPhase(handle, state.Motion.Pose, state.Inertia.Local, ref set.Collidables[activeBodyIndex]);
}
else
{
@@ -435,10 +514,11 @@ public void ApplyDescription(BodyHandle handle, in BodyDescription description)
ref var set = ref Sets[location.SetIndex];
ref var collidable = ref set.Collidables[location.Index];
var oldShape = collidable.Shape;
- var newlyKinematic = IsKinematic(description.LocalInertia) && !IsKinematic(set.LocalInertias[location.Index]);
+ var nowKinematic = IsKinematic(description.LocalInertia);
+ var previouslyKinematic = IsKinematicUnsafeGCHole(ref set.DynamicsState[location.Index].Inertia.Local);
set.ApplyDescriptionByIndex(location.Index, description);
UpdateForShapeChange(handle, location.Index, oldShape, description.Collidable.Shape);
- UpdateForKinematicStateChange(handle, ref location, ref set, newlyKinematic);
+ UpdateForKinematicStateChange(handle, ref location, ref set, previouslyKinematic, nowKinematic);
UpdateBounds(handle);
}
@@ -455,11 +535,26 @@ public void GetDescription(BodyHandle handle, out BodyDescription description)
set.GetDescription(location.Index, out description);
}
+ ///
+ /// Gets the description of a body by handle.
+ ///
+ /// Handle of the body to look up.
+ /// Description of the body.
+ public BodyDescription GetDescription(BodyHandle handle)
+ {
+ ValidateExistingHandle(handle);
+ ref var location = ref HandleToLocation[handle.Value];
+ ref var set = ref Sets[location.SetIndex];
+ set.GetDescription(location.Index, out var description);
+ return description;
+ }
+
///
/// Gets a reference to a body by its handle.
///
/// Handle of the body to grab a reference of.
/// Reference to the desired body.
+ /// This is an alias for and the constructor. They are all equivalent.
public BodyReference GetBodyReference(BodyHandle handle)
{
ValidateExistingHandle(handle);
@@ -478,6 +573,24 @@ public bool BodyExists(BodyHandle bodyHandle)
return bodyHandle.Value >= 0 && bodyHandle.Value < HandleToLocation.Length && HandleToLocation[bodyHandle.Value].SetIndex >= 0;
}
+ ///
+ /// Computes the number of bodies contained in the simulation.
+ ///
+ /// Number of bodies contained in the simulation.
+ /// Enumerates all instances in the collection, summing the body counts for every allocated instance.
+ /// For simulations with very large numbers of sleeping body sets, this is not a trivial operation.
+ public int CountBodies()
+ {
+ int count = 0;
+ for (int i = 0; i < Sets.Length; ++i)
+ {
+ ref var set = ref Sets[i];
+ if (set.Allocated)
+ count += set.Count;
+ }
+ return count;
+ }
+
[Conditional("DEBUG")]
internal void ValidateExistingHandle(BodyHandle handle)
{
@@ -504,18 +617,17 @@ internal void ValidateMotionStates()
{
for (int j = 0; j < set.Count; ++j)
{
- ref var pose = ref set.Poses[j];
- ref var velocity = ref set.Velocities[j];
+ ref var state = ref set.DynamicsState[j];
try
{
- pose.Position.Validate();
- pose.Orientation.ValidateOrientation();
- velocity.Linear.Validate();
- velocity.Angular.Validate();
+ state.Motion.Pose.Position.Validate();
+ state.Motion.Pose.Orientation.ValidateOrientation();
+ state.Motion.Velocity.Linear.Validate();
+ state.Motion.Velocity.Angular.Validate();
}
catch
{
- Console.WriteLine($"Validation failed on body {i} of set {j}. Position: {pose.Position}, orientation: {pose.Orientation}, linear: {velocity.Linear}, angular: {velocity.Angular}");
+ Console.WriteLine($"Validation failed on body {i} of set {j}. Position: {state.Motion.Pose.Position}, orientation: {state.Motion.Pose.Orientation}, linear: {state.Motion.Velocity.Linear}, angular: {state.Motion.Velocity.Angular}");
throw;
}
@@ -524,563 +636,34 @@ internal void ValidateMotionStates()
}
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void GatherInertiaForBody(ref BodyInertia source, ref BodyInertias targetSlot)
- {
- GatherScatter.GetFirst(ref targetSlot.InverseInertiaTensor.XX) = source.InverseInertiaTensor.XX;
- GatherScatter.GetFirst(ref targetSlot.InverseInertiaTensor.YX) = source.InverseInertiaTensor.YX;
- GatherScatter.GetFirst(ref targetSlot.InverseInertiaTensor.YY) = source.InverseInertiaTensor.YY;
- GatherScatter.GetFirst(ref targetSlot.InverseInertiaTensor.ZX) = source.InverseInertiaTensor.ZX;
- GatherScatter.GetFirst(ref targetSlot.InverseInertiaTensor.ZY) = source.InverseInertiaTensor.ZY;
- GatherScatter.GetFirst(ref targetSlot.InverseInertiaTensor.ZZ) = source.InverseInertiaTensor.ZZ;
- GatherScatter.GetFirst(ref targetSlot.InverseMass) = source.InverseMass;
- }
-
- ///
- /// Gathers inertia for one body bundle into an AOSOA bundle.
- ///
- /// Active body indices being gathered.
- /// Number of bodies in the bundle.
- /// Gathered inertia of body A.
- /// Gathered inertia of body B.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherInertia(ref Vector references, int count,
- out BodyInertias inertiaA)
- {
- Unsafe.SkipInit(out inertiaA);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references);
- for (int i = 0; i < count; ++i)
- {
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexA, i)], ref GatherScatter.GetOffsetInstance(ref inertiaA, i));
- }
- }
-
- ///
- /// Gathers inertia for two body bundles into AOSOA bundles.
- ///
- /// Active body indices being gathered.
- /// Number of bodies in the bundle.
- /// Gathered inertia of body A.
- /// Gathered inertia of body B.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherInertia(ref TwoBodyReferences references, int count, out BodyInertias inertiaA, out BodyInertias inertiaB)
- {
- Unsafe.SkipInit(out inertiaA);
- Unsafe.SkipInit(out inertiaB);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- for (int i = 0; i < count; ++i)
- {
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexA, i)], ref GatherScatter.GetOffsetInstance(ref inertiaA, i));
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexB, i)], ref GatherScatter.GetOffsetInstance(ref inertiaB, i));
- }
- }
-
- ///
- /// Gathers inertia for three body bundles into AOSOA bundles.
- ///
- /// Active body indices being gathered.
- /// Number of bodies in the bundle.
- /// Gathered inertia of body A.
- /// Gathered inertia of body B.
- /// Gathered inertia of body C.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherInertia(ref ThreeBodyReferences references, int count, out BodyInertias inertiaA, out BodyInertias inertiaB, out BodyInertias inertiaC)
- {
- Unsafe.SkipInit(out inertiaA);
- Unsafe.SkipInit(out inertiaB);
- Unsafe.SkipInit(out inertiaC);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- ref var baseIndexC = ref Unsafe.As, int>(ref references.IndexC);
- for (int i = 0; i < count; ++i)
- {
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexA, i)], ref GatherScatter.GetOffsetInstance(ref inertiaA, i));
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexB, i)], ref GatherScatter.GetOffsetInstance(ref inertiaB, i));
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexC, i)], ref GatherScatter.GetOffsetInstance(ref inertiaC, i));
- }
- }
-
- ///
- /// Gathers inertia for four body bundles into AOSOA bundles.
- ///
- /// Active body indices being gathered.
- /// Number of bodies in the bundle.
- /// Gathered inertia of body A.
- /// Gathered inertia of body B.
- /// Gathered inertia of body C.
- /// Gathered inertia of body D.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherInertia(ref FourBodyReferences references, int count, out BodyInertias inertiaA, out BodyInertias inertiaB, out BodyInertias inertiaC, out BodyInertias inertiaD)
- {
- Unsafe.SkipInit(out inertiaA);
- Unsafe.SkipInit(out inertiaB);
- Unsafe.SkipInit(out inertiaC);
- Unsafe.SkipInit(out inertiaD);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- ref var baseIndexC = ref Unsafe.As, int>(ref references.IndexC);
- ref var baseIndexD = ref Unsafe.As, int>(ref references.IndexD);
- for (int i = 0; i < count; ++i)
- {
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexA, i)], ref GatherScatter.GetOffsetInstance(ref inertiaA, i));
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexB, i)], ref GatherScatter.GetOffsetInstance(ref inertiaB, i));
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexC, i)], ref GatherScatter.GetOffsetInstance(ref inertiaC, i));
- GatherInertiaForBody(ref Inertias[Unsafe.Add(ref baseIndexD, i)], ref GatherScatter.GetOffsetInstance(ref inertiaD, i));
- }
- }
- ///
- /// Gathers orientations for two body bundles into AOSOA bundles.
- ///
- /// Active body indices being gathered.
- /// Number of body pairs in the bundle.
- /// Gathered orientation of body A.
- /// Gathered orientation of body B.
- //[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherOrientation(ref TwoBodyReferences references, int count,
- out QuaternionWide orientationA, out QuaternionWide orientationB)
+ internal void ValidateAwakeMotionStatesByHash(HashDiagnosticType type)
{
- Unsafe.SkipInit(out orientationA);
- Unsafe.SkipInit(out orientationB);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
-
- ref var poses = ref ActiveSet.Poses;
- for (int i = 0; i < count; ++i)
- {
- ref var indexA = ref Unsafe.Add(ref baseIndexA, i);
- QuaternionWide.WriteFirst(poses[indexA].Orientation, ref GatherScatter.GetOffsetInstance(ref orientationA, i));
-
- ref var indexB = ref Unsafe.Add(ref baseIndexB, i);
- QuaternionWide.WriteFirst(poses[indexB].Orientation, ref GatherScatter.GetOffsetInstance(ref orientationB, i));
- }
- }
-
- ///
- /// Gathers orientations for one body bundles into AOSOA bundles.
- ///
- /// Active body indices being gathered.
- /// Number of body pairs in the bundle.
- /// Gathered orientation of bodies in the bundle.
- //[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherOrientation(ref Vector references, int count,
- out QuaternionWide orientation)
- {
- Unsafe.SkipInit(out orientation);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references);
-
- ref var poses = ref ActiveSet.Poses;
- for (int i = 0; i < count; ++i)
- {
- ref var indexA = ref Unsafe.Add(ref baseIndexA, i);
- QuaternionWide.WriteFirst(poses[indexA].Orientation, ref GatherScatter.GetOffsetInstance(ref orientation, i));
- }
- }
-
-
-
- ///
- /// Gathers pose information for a body bundle into an AOSOA bundle.
- ///
- /// Active body indices being gathered.
- /// Number of body pairs in the bundle.
- /// Gathered absolute position of the body.
- /// Gathered orientation of the body.
- //[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherPose(ref Vector references, int count, out Vector3Wide position, out QuaternionWide orientation)
- {
- Unsafe.SkipInit(out position);
- Unsafe.SkipInit(out orientation);
- //TODO: This function and its users (which should be relatively few) is a problem for large world position precision.
- //It directly reports the position, thereby infecting vectorized logic with the high precision representation.
- //You might be able to redesign the users of this function to not need it, but that comes with its own difficulties
- //(for example, making the grab motor rely on having its goal offset updated every frame by the user).
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndex = ref Unsafe.As, int>(ref references);
-
- ref var poses = ref ActiveSet.Poses;
- for (int i = 0; i < count; ++i)
- {
- ref var indexA = ref Unsafe.Add(ref baseIndex, i);
- ref var poseA = ref poses[indexA];
- Vector3Wide.WriteFirst(poseA.Position, ref GatherScatter.GetOffsetInstance(ref position, i));
- QuaternionWide.WriteFirst(poseA.Orientation, ref GatherScatter.GetOffsetInstance(ref orientation, i));
-
- }
- }
-
- ///
- /// Gathers orientations and relative positions for a two body bundle into an AOSOA bundle.
- ///
- /// Active body indices being gathered.
- /// Number of body pairs in the bundle.
- /// Gathered offsets from body A to body B.
- /// Gathered orientation of body A.
- /// Gathered orientation of body B.
- //[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherPose(ref TwoBodyReferences references, int count,
- out Vector3Wide offsetB, out QuaternionWide orientationA, out QuaternionWide orientationB)
- {
- Unsafe.SkipInit(out Vector3Wide positionA);
- Unsafe.SkipInit(out Vector3Wide positionB);
- Unsafe.SkipInit(out orientationA);
- Unsafe.SkipInit(out orientationB);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
-
- ref var poses = ref ActiveSet.Poses;
- for (int i = 0; i < count; ++i)
- {
- ref var indexA = ref Unsafe.Add(ref baseIndexA, i);
- ref var poseA = ref poses[indexA];
- Vector3Wide.WriteFirst(poseA.Position, ref GatherScatter.GetOffsetInstance(ref positionA, i));
- QuaternionWide.WriteFirst(poseA.Orientation, ref GatherScatter.GetOffsetInstance(ref orientationA, i));
-
- ref var indexB = ref Unsafe.Add(ref baseIndexB, i);
- ref var poseB = ref poses[indexB];
- Vector3Wide.WriteFirst(poseB.Position, ref GatherScatter.GetOffsetInstance(ref positionB, i));
- QuaternionWide.WriteFirst(poseB.Orientation, ref GatherScatter.GetOffsetInstance(ref orientationB, i));
- }
- //TODO: In future versions, we will likely store the body position in different forms to allow for extremely large worlds.
- //That will be an opt-in feature. The default implementation will use the FP32 representation, but the user could choose to swap it out for a fp64 or fixed64 representation.
- //This affects other systems- AABB calculation, pose integration, solving, and in extreme (64 bit) cases, the broadphase.
- //We want to insulate other systems from direct knowledge about the implementation of positions when possible.
- //These functions support the solver's needs while hiding absolute positions.
- //In order to support other absolute positions, we'll need alternate implementations of this and other functions.
- //But for the most part, we don't want to pay the overhead of an abstract invocation within the inner loop of the solver.
- //Given the current limits of C# and the compiler, the best option seems to be conditional compilation.
- Vector3Wide.Subtract(positionB, positionA, out offsetB);
- }
-
- ///
- /// Gathers relative positions for a two body bundle into an AOSOA bundle.
- ///
- /// Active body indices being gathered.
- /// Number of body pairs in the bundle.
- /// Gathered offset from body A to of body B.
- //[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherOffsets(ref TwoBodyReferences references, int count, out Vector3Wide ab)
- {
- Unsafe.SkipInit(out Vector3Wide positionA);
- Unsafe.SkipInit(out Vector3Wide positionB);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
-
- ref var poses = ref ActiveSet.Poses;
- for (int i = 0; i < count; ++i)
- {
- Vector3Wide.WriteFirst(poses[Unsafe.Add(ref baseIndexA, i)].Position, ref GatherScatter.GetOffsetInstance(ref positionA, i));
- Vector3Wide.WriteFirst(poses[Unsafe.Add(ref baseIndexB, i)].Position, ref GatherScatter.GetOffsetInstance(ref positionB, i));
- }
- //Same as other gather case; this is sensitive to changes in the representation of body position. In high precision modes, this'll need to change.
- Vector3Wide.Subtract(positionB, positionA, out ab);
- }
-
- ///
- /// Gathers relative positions for a three body bundle into an AOSOA bundle.
- ///
- /// Active body indices being gathered.
- /// Number of body pairs in the bundle.
- /// Gathered offset from body A to of body B.
- /// Gathered offset from body A to of body C.
- //[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherOffsets(ref ThreeBodyReferences references, int count, out Vector3Wide ab, out Vector3Wide ac)
- {
- Unsafe.SkipInit(out Vector3Wide positionA);
- Unsafe.SkipInit(out Vector3Wide positionB);
- Unsafe.SkipInit(out Vector3Wide positionC);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- ref var baseIndexC = ref Unsafe.As, int>(ref references.IndexC);
-
- ref var poses = ref ActiveSet.Poses;
- for (int i = 0; i < count; ++i)
- {
- Vector3Wide.WriteFirst(poses[Unsafe.Add(ref baseIndexA, i)].Position, ref GatherScatter.GetOffsetInstance(ref positionA, i));
- Vector3Wide.WriteFirst(poses[Unsafe.Add(ref baseIndexB, i)].Position, ref GatherScatter.GetOffsetInstance(ref positionB, i));
- Vector3Wide.WriteFirst(poses[Unsafe.Add(ref baseIndexC, i)].Position, ref GatherScatter.GetOffsetInstance(ref positionC, i));
- }
- //Same as two body case; this is sensitive to changes in the representation of body position. In high precision modes, this'll need to change.
- Vector3Wide.Subtract(positionB, positionA, out ab);
- Vector3Wide.Subtract(positionC, positionA, out ac);
- }
- ///
- /// Gathers relative positions for a four body bundle into an AOSOA bundle.
- ///
- /// Active body indices being gathered.
- /// Number of body pairs in the bundle.
- /// Gathered offset from body A to of body B.
- /// Gathered offset from body A to of body C.
- /// Gathered offset from body A to of body D.
- //[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GatherOffsets(ref FourBodyReferences references, int count, out Vector3Wide ab, out Vector3Wide ac, out Vector3Wide ad)
- {
- Unsafe.SkipInit(out Vector3Wide positionA);
- Unsafe.SkipInit(out Vector3Wide positionB);
- Unsafe.SkipInit(out Vector3Wide positionC);
- Unsafe.SkipInit(out Vector3Wide positionD);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- ref var baseIndexC = ref Unsafe.As, int>(ref references.IndexC);
- ref var baseIndexD = ref Unsafe.As, int>(ref references.IndexD);
-
- ref var poses = ref ActiveSet.Poses;
- for (int i = 0; i < count; ++i)
- {
- Vector3Wide.WriteFirst(poses[Unsafe.Add(ref baseIndexA, i)].Position, ref GatherScatter.GetOffsetInstance(ref positionA, i));
- Vector3Wide.WriteFirst(poses[Unsafe.Add(ref baseIndexB, i)].Position, ref GatherScatter.GetOffsetInstance(ref positionB, i));
- Vector3Wide.WriteFirst(poses[Unsafe.Add(ref baseIndexC, i)].Position, ref GatherScatter.GetOffsetInstance(ref positionC, i));
- Vector3Wide.WriteFirst(poses[Unsafe.Add(ref baseIndexD, i)].Position, ref GatherScatter.GetOffsetInstance(ref positionD, i));
- }
- //Same as two body case; this is sensitive to changes in the representation of body position. In high precision modes, this'll need to change.
- Vector3Wide.Subtract(positionB, positionA, out ab);
- Vector3Wide.Subtract(positionC, positionA, out ac);
- Vector3Wide.Subtract(positionD, positionA, out ad);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static unsafe void GatherVelocities(ref Buffer sources, ref BodyVelocities target, ref int baseIndex, int innerIndex)
- {
- ref var targetSlot = ref GatherScatter.GetOffsetInstance(ref target, innerIndex);
- ref var source = ref sources[Unsafe.Add(ref baseIndex, innerIndex)];
- GatherScatter.GetFirst(ref targetSlot.Linear.X) = source.Linear.X;
- GatherScatter.GetFirst(ref targetSlot.Linear.Y) = source.Linear.Y;
- GatherScatter.GetFirst(ref targetSlot.Linear.Z) = source.Linear.Z;
- GatherScatter.GetFirst(ref targetSlot.Angular.X) = source.Angular.X;
- GatherScatter.GetFirst(ref targetSlot.Angular.Y) = source.Angular.Y;
- GatherScatter.GetFirst(ref targetSlot.Angular.Z) = source.Angular.Z;
- }
-
- ///
- /// Gathers velocities for one body bundle and stores it into a velocity bundle.
- ///
- /// Active set indices of the bodies to gather velocity data for.
- /// Number of bodies in the bundle.
- /// Gathered velocities.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void GatherVelocities(ref Buffer sourceVelocities, ref Vector references, int count, out BodyVelocities velocities)
- {
- Unsafe.SkipInit(out velocities);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndex = ref Unsafe.As, int>(ref references);
- for (int i = 0; i < count; ++i)
- {
- GatherVelocities(ref sourceVelocities, ref velocities, ref baseIndex, i);
- }
- }
-
- ///
- /// Gathers velocities for two body bundles and stores it into velocity bundles.
- ///
- /// Active set indices of the bodies to gather velocity data for.
- /// Number of body pairs in the bundle.
- /// Gathered velocities of A bodies.
- /// Gathered velocities of B bodies.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void GatherVelocities(ref Buffer sourceVelocities, ref TwoBodyReferences references, int count, out BodyVelocities velocitiesA, out BodyVelocities velocitiesB)
- {
- Unsafe.SkipInit(out velocitiesA);
- Unsafe.SkipInit(out velocitiesB);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- for (int i = 0; i < count; ++i)
- {
- GatherVelocities(ref sourceVelocities, ref velocitiesA, ref baseIndexA, i);
- GatherVelocities(ref sourceVelocities, ref velocitiesB, ref baseIndexB, i);
- }
- }
-
- ///
- /// Gathers velocities for three body bundles and stores it into velocity bundles.
- ///
- /// Active set indices of the bodies to gather velocity data for.
- /// Number of body pairs in the bundle.
- /// Gathered velocities of A bodies.
- /// Gathered velocities of B bodies.
- /// Gathered velocities of C bodies.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void GatherVelocities(ref Buffer sourceVelocities, ref ThreeBodyReferences references, int count,
- out BodyVelocities velocitiesA, out BodyVelocities velocitiesB, out BodyVelocities velocitiesC)
- {
- Unsafe.SkipInit(out velocitiesA);
- Unsafe.SkipInit(out velocitiesB);
- Unsafe.SkipInit(out velocitiesC);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- ref var baseIndexC = ref Unsafe.As, int>(ref references.IndexC);
- for (int i = 0; i < count; ++i)
- {
- GatherVelocities(ref sourceVelocities, ref velocitiesA, ref baseIndexA, i);
- GatherVelocities(ref sourceVelocities, ref velocitiesB, ref baseIndexB, i);
- GatherVelocities(ref sourceVelocities, ref velocitiesC, ref baseIndexC, i);
- }
- }
-
- ///
- /// Gathers velocities for four body bundles and stores it into velocity bundles.
- ///
- /// Active set indices of the bodies to gather velocity data for.
- /// Number of body pairs in the bundle.
- /// Gathered velocities of A bodies.
- /// Gathered velocities of B bodies.
- /// Gathered velocities of C bodies.
- /// Gathered velocities of D bodies.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void GatherVelocities(ref Buffer sourceVelocities, ref FourBodyReferences references, int count,
- out BodyVelocities velocitiesA, out BodyVelocities velocitiesB, out BodyVelocities velocitiesC, out BodyVelocities velocitiesD)
- {
- Unsafe.SkipInit(out velocitiesA);
- Unsafe.SkipInit(out velocitiesB);
- Unsafe.SkipInit(out velocitiesC);
- Unsafe.SkipInit(out velocitiesD);
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- ref var baseIndexC = ref Unsafe.As, int>(ref references.IndexC);
- ref var baseIndexD = ref Unsafe.As, int>(ref references.IndexD);
- for (int i = 0; i < count; ++i)
- {
- GatherVelocities(ref sourceVelocities, ref velocitiesA, ref baseIndexA, i);
- GatherVelocities(ref sourceVelocities, ref velocitiesB, ref baseIndexB, i);
- GatherVelocities(ref sourceVelocities, ref velocitiesC, ref baseIndexC, i);
- GatherVelocities(ref sourceVelocities, ref velocitiesD, ref baseIndexD, i);
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static unsafe void ScatterVelocities(ref BodyVelocities sourceVelocities, ref Buffer targets, ref int baseIndex, int innerIndex)
- {
- //TODO: How much value would there be in branching on kinematic state and avoiding a write? Depends a lot on the number of kinematics.
- ref var sourceSlot = ref GatherScatter.GetOffsetInstance(ref sourceVelocities, innerIndex);
- ref var target = ref targets[Unsafe.Add(ref baseIndex, innerIndex)];
- target.Linear.X = GatherScatter.GetFirst(ref sourceSlot.Linear.X);
- target.Linear.Y = GatherScatter.GetFirst(ref sourceSlot.Linear.Y);
- target.Linear.Z = GatherScatter.GetFirst(ref sourceSlot.Linear.Z);
- target.Angular.X = GatherScatter.GetFirst(ref sourceSlot.Angular.X);
- target.Angular.Y = GatherScatter.GetFirst(ref sourceSlot.Angular.Y);
- target.Angular.Z = GatherScatter.GetFirst(ref sourceSlot.Angular.Z);
- }
-
-
- ///
- /// Scatters velocities for one body bundle into the active body set.
- ///
- /// Velocities of body bundle A to scatter.
- /// Active set indices of the bodies to scatter velocity data to.
- /// Number of body pairs in the bundle.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void ScatterVelocities(ref BodyVelocities sourceVelocities, ref Buffer targetVelocities, ref Vector references, int count)
- {
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndex = ref Unsafe.As, int>(ref references);
- for (int i = 0; i < count; ++i)
- {
- ScatterVelocities(ref sourceVelocities, ref targetVelocities, ref baseIndex, i);
- }
- }
-
- ///
- /// Scatters velocities for two body bundles into the active body set.
- ///
- /// Velocities of body bundle A to scatter.
- /// Velocities of body bundle B to scatter.
- /// Active set indices of the bodies to scatter velocity data to.
- /// Number of body pairs in the bundle.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void ScatterVelocities(ref BodyVelocities sourceVelocitiesA, ref BodyVelocities sourceVelocitiesB, ref Buffer targetVelocities,
- ref TwoBodyReferences references, int count)
- {
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- for (int i = 0; i < count; ++i)
- {
- ScatterVelocities(ref sourceVelocitiesA, ref targetVelocities, ref baseIndexA, i);
- ScatterVelocities(ref sourceVelocitiesB, ref targetVelocities, ref baseIndexB, i);
- }
- }
-
- ///
- /// Scatters velocities for three body bundles into the active body set.
- ///
- /// Velocities of body bundle A to scatter.
- /// Velocities of body bundle B to scatter.
- /// Velocities of body bundle A to scatter.
- /// Active set indices of the bodies to scatter velocity data to.
- /// Number of body pairs in the bundle.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void ScatterVelocities(
- ref BodyVelocities sourceVelocitiesA, ref BodyVelocities sourceVelocitiesB, ref BodyVelocities sourceVelocitiesC,
- ref Buffer targetVelocities, ref ThreeBodyReferences references, int count)
- {
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- ref var baseIndexC = ref Unsafe.As, int>(ref references.IndexC);
- for (int i = 0; i < count; ++i)
+ var instance = InvasiveHashDiagnostics.Instance;
+ ref int hash = ref instance.GetHashForType(type);
+ ref var set = ref ActiveSet;
+ for (int j = 0; j < set.Count; ++j)
{
- ScatterVelocities(ref sourceVelocitiesA, ref targetVelocities, ref baseIndexA, i);
- ScatterVelocities(ref sourceVelocitiesB, ref targetVelocities, ref baseIndexB, i);
- ScatterVelocities(ref sourceVelocitiesC, ref targetVelocities, ref baseIndexC, i);
+ ref var state = ref set.DynamicsState[j];
+ instance.ContributeToHash(ref hash, state.Motion.Pose.Position);
+ instance.ContributeToHash(ref hash, state.Motion.Pose.Orientation);
+ instance.ContributeToHash(ref hash, state.Motion.Velocity.Linear);
+ instance.ContributeToHash(ref hash, state.Motion.Velocity.Angular);
+ instance.ContributeToHash(ref hash, state.Inertia.Local.InverseInertiaTensor);
+ instance.ContributeToHash(ref hash, state.Inertia.Local.InverseMass);
+ instance.ContributeToHash(ref hash, state.Inertia.World.InverseInertiaTensor);
+ instance.ContributeToHash(ref hash, state.Inertia.World.InverseMass);
}
}
- ///
- /// Scatters velocities for four body bundles into the active body set.
- ///
- /// Velocities of body bundle A to scatter.
- /// Velocities of body bundle B to scatter.
- /// Velocities of body bundle A to scatter.
- /// Velocities of body bundle B to scatter.
- /// Active set indices of the bodies to scatter velocity data to.
- /// Number of body pairs in the bundle.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void ScatterVelocities(
- ref BodyVelocities sourceVelocitiesA, ref BodyVelocities sourceVelocitiesB, ref BodyVelocities sourceVelocitiesC, ref BodyVelocities sourceVelocitiesD,
- ref Buffer targetVelocities, ref FourBodyReferences references, int count)
+ internal void ValidateAwakeCollidablesByHash(HashDiagnosticType type)
{
- Debug.Assert(count >= 0 && count <= Vector.Count);
- //Grab the base references for the body indices. Note that we make use of the references memory layout again.
- ref var baseIndexA = ref Unsafe.As, int>(ref references.IndexA);
- ref var baseIndexB = ref Unsafe.As, int>(ref references.IndexB);
- ref var baseIndexC = ref Unsafe.As, int>(ref references.IndexC);
- ref var baseIndexD = ref Unsafe.As, int>(ref references.IndexD);
- for (int i = 0; i < count; ++i)
+ var instance = InvasiveHashDiagnostics.Instance;
+ ref int hash = ref instance.GetHashForType(type);
+ ref var set = ref ActiveSet;
+ for (int j = 0; j < set.Count; ++j)
{
- ScatterVelocities(ref sourceVelocitiesA, ref targetVelocities, ref baseIndexA, i);
- ScatterVelocities(ref sourceVelocitiesB, ref targetVelocities, ref baseIndexB, i);
- ScatterVelocities(ref sourceVelocitiesC, ref targetVelocities, ref baseIndexC, i);
- ScatterVelocities(ref sourceVelocitiesD, ref targetVelocities, ref baseIndexD, i);
+ instance.ContributeToHash(ref hash, set.Collidables[j]);
}
}
@@ -1120,13 +703,14 @@ struct ActiveConstraintBodyHandleEnumerator : IForEach wh
public int SourceBodyIndex;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void LoopBody(int connectedBodyIndex)
+ public void LoopBody(int encodedBodyIndex)
{
- if (SourceBodyIndex != connectedBodyIndex)
+ var bodyIndex = encodedBodyIndex & BodyReferenceMask;
+ if (SourceBodyIndex != bodyIndex)
{
//This enumerator is associated with the public connected bodies enumerator function. The user supplies a handle and expects handles in return, so we
//must convert the solver-provided indices to handles.
- InnerEnumerator.LoopBody(bodies.ActiveSet.IndexToHandle[connectedBodyIndex]);
+ InnerEnumerator.LoopBody(bodies.ActiveSet.IndexToHandle[bodyIndex]);
}
}
@@ -1158,7 +742,6 @@ public void LoopBody(int connectedBodyHandle)
/// Type of the enumerator to execute on each connected body.
/// Index of the active body to enumerate the connections of. This body will not appear in the set of enumerated bodies, even if it is connected to itself somehow.
/// Enumerator instance to run on each connected body.
- /// Solver from which to pull constraint body references.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void EnumerateConnectedBodyIndices(int activeBodyIndex, ref TEnumerator enumerator) where TEnumerator : IForEach
{
@@ -1171,7 +754,7 @@ internal void EnumerateConnectedBodyIndices(int activeBodyIndex, re
//Non-reversed iteration would result in skipped elements if the loop body removed anything. This relies on convention; any remover should be aware of this order.
for (int i = list.Count - 1; i >= 0; --i)
{
- solver.EnumerateConnectedBodies(list[i].ConnectingConstraintHandle, ref constraintBodiesEnumerator);
+ solver.EnumerateConnectedBodyReferences(list[i].ConnectingConstraintHandle, ref constraintBodiesEnumerator);
}
//Note that we have to assume the enumerator contains state mutated by the internal loop bodies.
//If it's a value type, those mutations won't be reflected in the original reference.
@@ -1185,7 +768,6 @@ internal void EnumerateConnectedBodyIndices(int activeBodyIndex, re
/// Type of the enumerator to execute on each connected body.
/// Handle of the body to enumerate the connections of. This body will not appear in the set of enumerated bodies, even if it is connected to itself somehow.
/// Enumerator instance to run on each connected body.
- /// Solver from which to pull constraint body references.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void EnumerateConnectedBodies(BodyHandle bodyHandle, ref TEnumerator enumerator) where TEnumerator : IForEach
{
@@ -1204,7 +786,7 @@ public void EnumerateConnectedBodies(BodyHandle bodyHandle, ref TEn
for (int i = list.Count - 1; i >= 0; --i)
{
- solver.EnumerateConnectedBodies(list[i].ConnectingConstraintHandle, ref constraintBodiesEnumerator);
+ solver.EnumerateConnectedRawBodyReferences(list[i].ConnectingConstraintHandle, ref constraintBodiesEnumerator);
}
enumerator = constraintBodiesEnumerator.InnerEnumerator;
}
@@ -1217,7 +799,7 @@ public void EnumerateConnectedBodies(BodyHandle bodyHandle, ref TEn
for (int i = list.Count - 1; i >= 0; --i)
{
- solver.EnumerateConnectedBodies(list[i].ConnectingConstraintHandle, ref constraintBodiesEnumerator);
+ solver.EnumerateConnectedRawBodyReferences(list[i].ConnectingConstraintHandle, ref constraintBodiesEnumerator);
}
enumerator = constraintBodiesEnumerator.InnerEnumerator;
}
@@ -1258,31 +840,6 @@ unsafe void ResizeHandles(int newCapacity)
}
}
}
- //Note that these resize and ensure capacity functions affect only the active set.
- //Inactive islands are created with minimal allocations. Since you cannot add to or remove from inactive islands, it is pointless to try to modify their allocation sizes.
- ///
- /// Reallocates the inertias buffer for the target capacity. Will not shrink below the size of the current active set.
- ///
- internal void ResizeInertias(int capacity)
- {
- var targetCapacity = BufferPool.GetCapacityForCount(Math.Max(capacity, ActiveSet.Count));
- if (Inertias.Length != targetCapacity)
- {
- Pool.ResizeToAtLeast(ref Inertias, targetCapacity, Math.Min(Inertias.Length, ActiveSet.Count));
- }
- }
- ///
- /// Guarantees that the inertias capacity is sufficient for the given capacity.
- ///
- internal void EnsureInertiasCapacity(int capacity)
- {
- if (capacity < ActiveSet.Count)
- capacity = ActiveSet.Count;
- if (Inertias.Length < capacity)
- {
- Pool.ResizeToAtLeast(ref Inertias, capacity, Math.Min(Inertias.Length, ActiveSet.Count));
- }
- }
///
/// Resizes the allocated spans for active body data. Note that this is conservative; it will never orphan existing objects.
@@ -1295,7 +852,6 @@ public void Resize(int capacity)
{
ActiveSet.InternalResize(targetBodyCapacity, Pool);
}
- ResizeInertias(capacity);
var targetHandleCapacity = BufferPool.GetCapacityForCount(Math.Max(capacity, HandlePool.HighestPossiblyClaimedId + 1));
if (HandleToLocation.Length != targetHandleCapacity)
{
@@ -1328,7 +884,6 @@ public void EnsureCapacity(int capacity)
{
ActiveSet.InternalResize(capacity, Pool);
}
- EnsureInertiasCapacity(capacity);
if (HandleToLocation.Length < capacity)
{
ResizeHandles(capacity);
@@ -1363,8 +918,6 @@ public void Dispose()
}
}
Pool.Return(ref Sets);
- if (Inertias.Allocated)
- Pool.Return(ref Inertias);
Pool.Return(ref HandleToLocation);
HandlePool.Dispose(Pool);
}
diff --git a/BepuPhysics/Bodies_GatherScatter.cs b/BepuPhysics/Bodies_GatherScatter.cs
new file mode 100644
index 000000000..ffebddca1
--- /dev/null
+++ b/BepuPhysics/Bodies_GatherScatter.cs
@@ -0,0 +1,755 @@
+using BepuUtilities.Memory;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using BepuPhysics.Constraints;
+using BepuUtilities;
+using static BepuUtilities.GatherScatter;
+using System.Runtime.Intrinsics.X86;
+using System.Runtime.Intrinsics;
+
+namespace BepuPhysics
+{
+ public partial class Bodies
+ {
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteGatherInertia(int index, int bodyIndexInBundle, ref Buffer states, ref BodyInertiaWide gatheredInertias)
+ {
+ ref var source = ref states[index].Inertia.World;
+ ref var targetSlot = ref GetOffsetInstance(ref gatheredInertias, bodyIndexInBundle);
+ GetFirst(ref targetSlot.InverseInertiaTensor.XX) = source.InverseInertiaTensor.XX;
+ GetFirst(ref targetSlot.InverseInertiaTensor.YX) = source.InverseInertiaTensor.YX;
+ GetFirst(ref targetSlot.InverseInertiaTensor.YY) = source.InverseInertiaTensor.YY;
+ GetFirst(ref targetSlot.InverseInertiaTensor.ZX) = source.InverseInertiaTensor.ZX;
+ GetFirst(ref targetSlot.InverseInertiaTensor.ZY) = source.InverseInertiaTensor.ZY;
+ GetFirst(ref targetSlot.InverseInertiaTensor.ZZ) = source.InverseInertiaTensor.ZZ;
+ GetFirst(ref targetSlot.InverseMass) = source.InverseMass;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void WriteGatherMotionState(int index, int bodyIndexInBundle, ref Buffer states,
+ ref Vector3Wide position, ref QuaternionWide orientation, ref BodyVelocityWide velocity)
+ {
+ ref var state = ref states[index].Motion;
+ Vector3Wide.WriteFirst(state.Pose.Position, ref GetOffsetInstance(ref position, bodyIndexInBundle));
+ QuaternionWide.WriteFirst(state.Pose.Orientation, ref GetOffsetInstance(ref orientation, bodyIndexInBundle));
+ Vector3Wide.WriteFirst(state.Velocity.Linear, ref GetOffsetInstance(ref velocity.Linear, bodyIndexInBundle));
+ Vector3Wide.WriteFirst(state.Velocity.Angular, ref GetOffsetInstance(ref velocity.Angular, bodyIndexInBundle));
+ }
+
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ unsafe static void FallbackGatherMotionState(BodyDynamics* states, Vector encodedBodyIndices, ref Vector3Wide position, ref QuaternionWide orientation, ref BodyVelocityWide velocity)
+ {
+ var pPositionX = (float*)Unsafe.AsPointer(ref position.X);
+ var pPositionY = (float*)Unsafe.AsPointer(ref position.Y);
+ var pPositionZ = (float*)Unsafe.AsPointer(ref position.Z);
+ var pOrientationX = (float*)Unsafe.AsPointer(ref orientation.X);
+ var pOrientationY = (float*)Unsafe.AsPointer(ref orientation.Y);
+ var pOrientationZ = (float*)Unsafe.AsPointer(ref orientation.Z);
+ var pOrientationW = (float*)Unsafe.AsPointer(ref orientation.W);
+ var pLinearX = (float*)Unsafe.AsPointer(ref velocity.Linear.X);
+ var pLinearY = (float*)Unsafe.AsPointer(ref velocity.Linear.Y);
+ var pLinearZ = (float*)Unsafe.AsPointer(ref velocity.Linear.Z);
+ var pAngularX = (float*)Unsafe.AsPointer(ref velocity.Angular.X);
+ var pAngularY = (float*)Unsafe.AsPointer(ref velocity.Angular.Y);
+ var pAngularZ = (float*)Unsafe.AsPointer(ref velocity.Angular.Z);
+
+ for (int i = 0; i < Vector.Count; ++i)
+ {
+ var encodedBodyIndex = encodedBodyIndices[i];
+ if (encodedBodyIndex < 0)
+ continue;
+ var stateValues = (float*)(states + (encodedBodyIndex & BodyReferenceMask));
+ pPositionX[i] = stateValues[MotionState.OffsetToPositionX];
+ pPositionY[i] = stateValues[MotionState.OffsetToPositionY];
+ pPositionZ[i] = stateValues[MotionState.OffsetToPositionZ];
+ pOrientationX[i] = stateValues[MotionState.OffsetToOrientationX];
+ pOrientationY[i] = stateValues[MotionState.OffsetToOrientationY];
+ pOrientationZ[i] = stateValues[MotionState.OffsetToOrientationZ];
+ pOrientationW[i] = stateValues[MotionState.OffsetToOrientationW];
+ pLinearX[i] = stateValues[MotionState.OffsetToLinearX];
+ pLinearY[i] = stateValues[MotionState.OffsetToLinearY];
+ pLinearZ[i] = stateValues[MotionState.OffsetToLinearZ];
+ pAngularX[i] = stateValues[MotionState.OffsetToAngularX];
+ pAngularY[i] = stateValues[MotionState.OffsetToAngularY];
+ pAngularZ[i] = stateValues[MotionState.OffsetToAngularZ];
+ }
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ unsafe static void FallbackGatherInertia(BodyDynamics* states, Vector encodedBodyIndices, ref BodyInertiaWide inertia, int offsetInFloats)
+ {
+ var pMass = (float*)Unsafe.AsPointer(ref inertia.InverseMass);
+ var pInertiaXX = (float*)Unsafe.AsPointer(ref inertia.InverseInertiaTensor.XX);
+ var pInertiaYX = (float*)Unsafe.AsPointer(ref inertia.InverseInertiaTensor.YX);
+ var pInertiaYY = (float*)Unsafe.AsPointer(ref inertia.InverseInertiaTensor.YY);
+ var pInertiaZX = (float*)Unsafe.AsPointer(ref inertia.InverseInertiaTensor.ZX);
+ var pInertiaZY = (float*)Unsafe.AsPointer(ref inertia.InverseInertiaTensor.ZY);
+ var pInertiaZZ = (float*)Unsafe.AsPointer(ref inertia.InverseInertiaTensor.ZZ);
+
+ for (int i = 0; i < Vector.Count; ++i)
+ {
+ var encodedBodyIndex = encodedBodyIndices[i];
+ if (encodedBodyIndex < 0)
+ continue;
+ var inertiaValues = (float*)(states + (encodedBodyIndex & BodyReferenceMask)) + offsetInFloats;
+ pInertiaXX[i] = inertiaValues[0];
+ pInertiaYX[i] = inertiaValues[1];
+ pInertiaYY[i] = inertiaValues[2];
+ pInertiaZX[i] = inertiaValues[3];
+ pInertiaZY[i] = inertiaValues[4];
+ pInertiaZZ[i] = inertiaValues[5];
+ pMass[i] = inertiaValues[6];
+ }
+ }
+
+ public const int DoesntExistFlagIndex = 31;
+ public const int KinematicFlagIndex = 30;
+ public const int KinematicMask = 1 << KinematicFlagIndex;
+ ///
+ /// Constraint body references greater than a given unsigned value are either kinematic (bit 30 set) or correspond to an empty lane (bit 31 set).
+ ///
+ public const uint DynamicLimit = KinematicMask;
+ public const uint BodyReferenceMetadataMask = (1u << DoesntExistFlagIndex) | KinematicMask;
+ ///
+ /// Mask of bits containing the decoded body reference in a constraint body reference. For active constraints this would be the body index bits, for sleeping constraints this would be the body handle bits.
+ ///
+ public const int BodyReferenceMask = (int)~BodyReferenceMetadataMask;
+
+ ///
+ /// Checks whether a constraint encoded body reference value refers to a dynamic body.
+ ///
+ /// Raw encoded value taken from a constraint.
+ /// True if the encoded body reference refers to a dynamic body, false otherwise.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsEncodedDynamicReference(int encodedBodyReferenceValue)
+ {
+ return (uint)encodedBodyReferenceValue < DynamicLimit;
+ }
+ ///
+ /// Checks whether a constraint encoded body reference value refers to a kinematic body.
+ ///
+ /// Raw encoded value taken from a constraint.
+ /// True if the encoded body reference refers to a kinematic body, false otherwise.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool IsEncodedKinematicReference(int encodedBodyReferenceValue)
+ {
+ return (uint)encodedBodyReferenceValue >= DynamicLimit;
+ }
+
+ //TODO: Good argument for source generation (or at least refactoring) here. The vectorized paths don't need much in the way of maintenance, but having a bunch of duplicates is unavoidably error prone.
+
+ ///
+ /// Transposes of bundle of array-of-structures layout motion states into a bundle of array-of-structures-of-arrays layout.
+ /// Size of buffer must be no larger than the .
+ ///
+ /// Array-of-structures data to transpose.
+ /// Array-of-structures-of-arrays positions.
+ /// Array-of-structures-of-arrays orientations.
+ /// Array-of-structures-of-arrays velocities.
+ public static unsafe void TransposeMotionStates(Buffer states, out Vector3Wide position, out QuaternionWide orientation, out BodyVelocityWide velocity)
+ {
+ Debug.Assert(states.Length > 0 && states.Length <= Vector.Count);
+ if (Avx.IsSupported && Vector.Count == 8)
+ {
+ var empty1 = states.Length <= 1;
+ var empty2 = states.Length <= 2;
+ var empty3 = states.Length <= 3;
+ var empty4 = states.Length <= 4;
+ var empty5 = states.Length <= 5;
+ var empty6 = states.Length <= 6;
+ var empty7 = states.Length <= 7;
+
+ var s0 = (float*)states.Memory;
+ var s1 = (float*)(states.Memory + 1);
+ var s2 = (float*)(states.Memory + 2);
+ var s3 = (float*)(states.Memory + 3);
+ var s4 = (float*)(states.Memory + 4);
+ var s5 = (float*)(states.Memory + 5);
+ var s6 = (float*)(states.Memory + 6);
+ var s7 = (float*)(states.Memory + 7);
+
+ {
+ //Load every body for the first half of the motion state.
+ //Note that buffers are allocated on cache line boundaries, so we can use aligned loads for all that matters.
+ var m0 = Avx.LoadAlignedVector256(s0);
+ var m1 = empty1 ? Vector256.Zero : Avx.LoadAlignedVector256(s1);
+ var m2 = empty2 ? Vector256.Zero : Avx.LoadAlignedVector256(s2);
+ var m3 = empty3 ? Vector256.Zero : Avx.LoadAlignedVector256(s3);
+ var m4 = empty4 ? Vector256.Zero : Avx.LoadAlignedVector256(s4);
+ var m5 = empty5 ? Vector256.Zero : Avx.LoadAlignedVector256(s5);
+ var m6 = empty6 ? Vector256.Zero : Avx.LoadAlignedVector256(s6);
+ var m7 = empty7 ? Vector256.Zero : Avx.LoadAlignedVector256(s7);
+
+ var n0 = Avx.UnpackLow(m0, m1);
+ var n1 = Avx.UnpackLow(m2, m3);
+ var n2 = Avx.UnpackLow(m4, m5);
+ var n3 = Avx.UnpackLow(m6, m7);
+ var n4 = Avx.UnpackHigh(m0, m1);
+ var n5 = Avx.UnpackHigh(m2, m3);
+ var n6 = Avx.UnpackHigh(m4, m5);
+ var n7 = Avx.UnpackHigh(m6, m7);
+
+ var o0 = Avx.Shuffle(n0, n1, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o1 = Avx.Shuffle(n2, n3, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o2 = Avx.Shuffle(n4, n5, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o3 = Avx.Shuffle(n6, n7, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o4 = Avx.Shuffle(n0, n1, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o5 = Avx.Shuffle(n2, n3, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o6 = Avx.Shuffle(n4, n5, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o7 = Avx.Shuffle(n6, n7, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+
+ orientation.X = Avx.Permute2x128(o0, o1, 0 | (2 << 4)).AsVector();
+ orientation.Y = Avx.Permute2x128(o4, o5, 0 | (2 << 4)).AsVector();
+ orientation.Z = Avx.Permute2x128(o2, o3, 0 | (2 << 4)).AsVector();
+ orientation.W = Avx.Permute2x128(o6, o7, 0 | (2 << 4)).AsVector();
+
+ position.X = Avx.Permute2x128(o0, o1, 1 | (3 << 4)).AsVector();
+ position.Y = Avx.Permute2x128(o4, o5, 1 | (3 << 4)).AsVector();
+ position.Z = Avx.Permute2x128(o2, o3, 1 | (3 << 4)).AsVector();
+ }
+
+ {
+ //Second half.
+ var m0 = Avx.LoadAlignedVector256(s0 + 8);
+ var m1 = empty1 ? Vector256.Zero : Avx.LoadAlignedVector256(s1 + 8);
+ var m2 = empty2 ? Vector256.Zero : Avx.LoadAlignedVector256(s2 + 8);
+ var m3 = empty3 ? Vector256.Zero : Avx.LoadAlignedVector256(s3 + 8);
+ var m4 = empty4 ? Vector256.Zero : Avx.LoadAlignedVector256(s4 + 8);
+ var m5 = empty5 ? Vector256.Zero : Avx.LoadAlignedVector256(s5 + 8);
+ var m6 = empty6 ? Vector256.Zero : Avx.LoadAlignedVector256(s6 + 8);
+ var m7 = empty7 ? Vector256.Zero : Avx.LoadAlignedVector256(s7 + 8);
+
+ var n0 = Avx.UnpackLow(m0, m1);
+ var n1 = Avx.UnpackLow(m2, m3);
+ var n2 = Avx.UnpackLow(m4, m5);
+ var n3 = Avx.UnpackLow(m6, m7);
+ var n4 = Avx.UnpackHigh(m0, m1);
+ var n5 = Avx.UnpackHigh(m2, m3);
+ var n6 = Avx.UnpackHigh(m4, m5);
+ var n7 = Avx.UnpackHigh(m6, m7);
+
+ var o0 = Avx.Shuffle(n0, n1, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o1 = Avx.Shuffle(n2, n3, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o2 = Avx.Shuffle(n4, n5, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o3 = Avx.Shuffle(n6, n7, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o4 = Avx.Shuffle(n0, n1, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o5 = Avx.Shuffle(n2, n3, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+
+ velocity.Linear.X = Avx.Permute2x128(o0, o1, 0 | (2 << 4)).AsVector();
+ velocity.Linear.Y = Avx.Permute2x128(o4, o5, 0 | (2 << 4)).AsVector();
+ velocity.Linear.Z = Avx.Permute2x128(o2, o3, 0 | (2 << 4)).AsVector();
+
+ velocity.Angular.X = Avx.Permute2x128(o0, o1, 1 | (3 << 4)).AsVector();
+ velocity.Angular.Y = Avx.Permute2x128(o4, o5, 1 | (3 << 4)).AsVector();
+ velocity.Angular.Z = Avx.Permute2x128(o2, o3, 1 | (3 << 4)).AsVector();
+ }
+ }
+ else
+ {
+ Unsafe.SkipInit(out position);
+ Unsafe.SkipInit(out orientation);
+ Unsafe.SkipInit(out velocity);
+ for (int i = 0; i < states.Length; ++i)
+ {
+ ref var state = ref states[i];
+ Vector3Wide.WriteSlot(state.Pose.Position, i, ref position);
+ QuaternionWide.WriteSlot(state.Pose.Orientation, i, ref orientation);
+ Vector3Wide.WriteSlot(state.Velocity.Linear, i, ref velocity.Linear);
+ Vector3Wide.WriteSlot(state.Velocity.Angular, i, ref velocity.Angular);
+ }
+ }
+ }
+
+
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void GatherState(Vector encodedBodyIndices, bool worldInertia, out Vector3Wide position, out QuaternionWide orientation, out BodyVelocityWide velocity, out BodyInertiaWide inertia)
+ where TAccessFilter : unmanaged, IBodyAccessFilter
+ {
+ var solverStates = ActiveSet.DynamicsState.Memory;
+ Unsafe.SkipInit(out TAccessFilter filter);
+ if (Avx.IsSupported && Vector.Count == 8)
+ {
+ var bodyIndices0 = encodedBodyIndices[0];
+ var empty0 = bodyIndices0 < 0;
+ var s0 = (float*)(solverStates + (bodyIndices0 & BodyReferenceMask));
+ var bodyIndices1 = encodedBodyIndices[1];
+ var empty1 = bodyIndices1 < 0;
+ var s1 = (float*)(solverStates + (bodyIndices1 & BodyReferenceMask));
+ var bodyIndices2 = encodedBodyIndices[2];
+ var empty2 = bodyIndices2 < 0;
+ var s2 = (float*)(solverStates + (bodyIndices2 & BodyReferenceMask));
+ var bodyIndices3 = encodedBodyIndices[3];
+ var empty3 = bodyIndices3 < 0;
+ var s3 = (float*)(solverStates + (bodyIndices3 & BodyReferenceMask));
+ var bodyIndices4 = encodedBodyIndices[4];
+ var empty4 = bodyIndices4 < 0;
+ var s4 = (float*)(solverStates + (bodyIndices4 & BodyReferenceMask));
+ var bodyIndices5 = encodedBodyIndices[5];
+ var empty5 = bodyIndices5 < 0;
+ var s5 = (float*)(solverStates + (bodyIndices5 & BodyReferenceMask));
+ var bodyIndices6 = encodedBodyIndices[6];
+ var empty6 = bodyIndices6 < 0;
+ var s6 = (float*)(solverStates + (bodyIndices6 & BodyReferenceMask));
+ var bodyIndices7 = encodedBodyIndices[7];
+ var empty7 = bodyIndices7 < 0;
+ var s7 = (float*)(solverStates + (bodyIndices7 & BodyReferenceMask));
+
+ //for (int i = 0; i < 8; ++i)
+ //{
+ // s0[i] = i;
+ // s1[i] = i + 100;
+ // s2[i] = i + 200;
+ // s3[i] = i + 300;
+ // s4[i] = i + 400;
+ // s5[i] = i + 500;
+ // s6[i] = i + 600;
+ // s7[i] = i + 700;
+ //}
+
+ {
+ //Load every body for the first half of the motion state.
+ //Note that buffers are allocated on cache line boundaries, so we can use aligned loads for all that matters.
+ var m0 = empty0 ? Vector256.Zero : Avx.LoadAlignedVector256(s0);
+ var m1 = empty1 ? Vector256.Zero : Avx.LoadAlignedVector256(s1);
+ var m2 = empty2 ? Vector256.Zero : Avx.LoadAlignedVector256(s2);
+ var m3 = empty3 ? Vector256.Zero : Avx.LoadAlignedVector256(s3);
+ var m4 = empty4 ? Vector256.Zero : Avx.LoadAlignedVector256(s4);
+ var m5 = empty5 ? Vector256.Zero : Avx.LoadAlignedVector256(s5);
+ var m6 = empty6 ? Vector256.Zero : Avx.LoadAlignedVector256(s6);
+ var m7 = empty7 ? Vector256.Zero : Avx.LoadAlignedVector256(s7);
+
+ var n0 = Avx.UnpackLow(m0, m1);
+ var n1 = Avx.UnpackLow(m2, m3);
+ var n2 = Avx.UnpackLow(m4, m5);
+ var n3 = Avx.UnpackLow(m6, m7);
+ var n4 = Avx.UnpackHigh(m0, m1);
+ var n5 = Avx.UnpackHigh(m2, m3);
+ var n6 = Avx.UnpackHigh(m4, m5);
+ var n7 = Avx.UnpackHigh(m6, m7);
+
+ var o0 = Avx.Shuffle(n0, n1, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o1 = Avx.Shuffle(n2, n3, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o2 = Avx.Shuffle(n4, n5, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o3 = Avx.Shuffle(n6, n7, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o4 = Avx.Shuffle(n0, n1, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o5 = Avx.Shuffle(n2, n3, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o6 = Avx.Shuffle(n4, n5, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o7 = Avx.Shuffle(n6, n7, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+
+ if (filter.GatherOrientation)
+ {
+ orientation.X = Avx.Permute2x128(o0, o1, 0 | (2 << 4)).AsVector();
+ orientation.Y = Avx.Permute2x128(o4, o5, 0 | (2 << 4)).AsVector();
+ orientation.Z = Avx.Permute2x128(o2, o3, 0 | (2 << 4)).AsVector();
+ orientation.W = Avx.Permute2x128(o6, o7, 0 | (2 << 4)).AsVector();
+ }
+ else
+ {
+ Unsafe.SkipInit(out orientation);
+ }
+ if (filter.GatherPosition)
+ {
+ position.X = Avx.Permute2x128(o0, o1, 1 | (3 << 4)).AsVector();
+ position.Y = Avx.Permute2x128(o4, o5, 1 | (3 << 4)).AsVector();
+ position.Z = Avx.Permute2x128(o2, o3, 1 | (3 << 4)).AsVector();
+ }
+ else
+ {
+ Unsafe.SkipInit(out position);
+ }
+ }
+
+ {
+ //Second half.
+ var m0 = empty0 ? Vector256.Zero : Avx.LoadAlignedVector256(s0 + 8);
+ var m1 = empty1 ? Vector256.Zero : Avx.LoadAlignedVector256(s1 + 8);
+ var m2 = empty2 ? Vector256.Zero : Avx.LoadAlignedVector256(s2 + 8);
+ var m3 = empty3 ? Vector256.Zero : Avx.LoadAlignedVector256(s3 + 8);
+ var m4 = empty4 ? Vector256.Zero : Avx.LoadAlignedVector256(s4 + 8);
+ var m5 = empty5 ? Vector256.Zero : Avx.LoadAlignedVector256(s5 + 8);
+ var m6 = empty6 ? Vector256.Zero : Avx.LoadAlignedVector256(s6 + 8);
+ var m7 = empty7 ? Vector256.Zero : Avx.LoadAlignedVector256(s7 + 8);
+
+ var n0 = Avx.UnpackLow(m0, m1);
+ var n1 = Avx.UnpackLow(m2, m3);
+ var n2 = Avx.UnpackLow(m4, m5);
+ var n3 = Avx.UnpackLow(m6, m7);
+ var n4 = Avx.UnpackHigh(m0, m1);
+ var n5 = Avx.UnpackHigh(m2, m3);
+ var n6 = Avx.UnpackHigh(m4, m5);
+ var n7 = Avx.UnpackHigh(m6, m7);
+
+ var o0 = Avx.Shuffle(n0, n1, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o1 = Avx.Shuffle(n2, n3, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o2 = Avx.Shuffle(n4, n5, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o3 = Avx.Shuffle(n6, n7, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o4 = Avx.Shuffle(n0, n1, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o5 = Avx.Shuffle(n2, n3, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+
+ if (filter.AccessLinearVelocity)
+ {
+ velocity.Linear.X = Avx.Permute2x128(o0, o1, 0 | (2 << 4)).AsVector();
+ velocity.Linear.Y = Avx.Permute2x128(o4, o5, 0 | (2 << 4)).AsVector();
+ velocity.Linear.Z = Avx.Permute2x128(o2, o3, 0 | (2 << 4)).AsVector();
+ }
+ else
+ {
+ Unsafe.SkipInit(out velocity.Linear);
+ }
+ if (filter.AccessAngularVelocity)
+ {
+ velocity.Angular.X = Avx.Permute2x128(o0, o1, 1 | (3 << 4)).AsVector();
+ velocity.Angular.Y = Avx.Permute2x128(o4, o5, 1 | (3 << 4)).AsVector();
+ velocity.Angular.Z = Avx.Permute2x128(o2, o3, 1 | (3 << 4)).AsVector();
+ }
+ else
+ {
+ Unsafe.SkipInit(out velocity.Angular);
+ }
+ }
+
+ {
+ var offsetInFloats = worldInertia ? 24 : 16;
+
+ //Load every inertia vector.
+ //Assuming here that most bodies are dynamic, so it's not worth the extra work extracting a dynamic-only mask to avoid loading some kinematic zeroes.
+ var m0 = empty0 ? Vector256.Zero : Avx.LoadAlignedVector256(s0 + offsetInFloats);
+ var m1 = empty1 ? Vector256.Zero : Avx.LoadAlignedVector256(s1 + offsetInFloats);
+ var m2 = empty2 ? Vector256.Zero : Avx.LoadAlignedVector256(s2 + offsetInFloats);
+ var m3 = empty3 ? Vector256.Zero : Avx.LoadAlignedVector256(s3 + offsetInFloats);
+ var m4 = empty4 ? Vector256.Zero : Avx.LoadAlignedVector256(s4 + offsetInFloats);
+ var m5 = empty5 ? Vector256.Zero : Avx.LoadAlignedVector256(s5 + offsetInFloats);
+ var m6 = empty6 ? Vector256.Zero : Avx.LoadAlignedVector256(s6 + offsetInFloats);
+ var m7 = empty7 ? Vector256.Zero : Avx.LoadAlignedVector256(s7 + offsetInFloats);
+
+ var n0 = Avx.UnpackLow(m0, m1);
+ var n1 = Avx.UnpackLow(m2, m3);
+ var n2 = Avx.UnpackLow(m4, m5);
+ var n3 = Avx.UnpackLow(m6, m7);
+ var n4 = Avx.UnpackHigh(m0, m1);
+ var n5 = Avx.UnpackHigh(m2, m3);
+ var n6 = Avx.UnpackHigh(m4, m5);
+ var n7 = Avx.UnpackHigh(m6, m7);
+
+ var o0 = Avx.Shuffle(n0, n1, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o1 = Avx.Shuffle(n2, n3, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o2 = Avx.Shuffle(n4, n5, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o3 = Avx.Shuffle(n6, n7, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o4 = Avx.Shuffle(n0, n1, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o5 = Avx.Shuffle(n2, n3, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o6 = Avx.Shuffle(n4, n5, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o7 = Avx.Shuffle(n6, n7, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+
+ if (filter.GatherInertiaTensor)
+ {
+ inertia.InverseInertiaTensor.XX = Avx.Permute2x128(o0, o1, 0 | (2 << 4)).AsVector();
+ inertia.InverseInertiaTensor.YX = Avx.Permute2x128(o4, o5, 0 | (2 << 4)).AsVector();
+ inertia.InverseInertiaTensor.YY = Avx.Permute2x128(o2, o3, 0 | (2 << 4)).AsVector();
+ inertia.InverseInertiaTensor.ZX = Avx.Permute2x128(o6, o7, 0 | (2 << 4)).AsVector();
+ inertia.InverseInertiaTensor.ZY = Avx.Permute2x128(o0, o1, 1 | (3 << 4)).AsVector();
+ inertia.InverseInertiaTensor.ZZ = Avx.Permute2x128(o4, o5, 1 | (3 << 4)).AsVector();
+ }
+ else
+ {
+ Unsafe.SkipInit(out inertia.InverseInertiaTensor);
+ }
+ if (filter.GatherMass)
+ {
+ inertia.InverseMass = Avx.Permute2x128(o2, o3, 1 | (3 << 4)).AsVector();
+ }
+ else
+ {
+ Unsafe.SkipInit(out inertia.InverseMass);
+ }
+ }
+
+ }
+ else
+ {
+ Unsafe.SkipInit(out position);
+ Unsafe.SkipInit(out orientation);
+ Unsafe.SkipInit(out velocity);
+ Unsafe.SkipInit(out inertia);
+ FallbackGatherMotionState(solverStates, encodedBodyIndices, ref position, ref orientation, ref velocity);
+ FallbackGatherInertia(solverStates, encodedBodyIndices, ref inertia, worldInertia ? 24 : 16);
+ }
+ }
+
+ //Note that ScatterPose and ScatterInertia do not need to check body references for empty lanes or for kinematicity.
+ //The mask is only set for lanes which were subject to constraint integration responsibility; empty lanes and kinematics cannot be integrated by constraints.
+
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ScatterPose(
+ ref Vector3Wide position, ref QuaternionWide orientation, Vector encodedBodyIndices, Vector mask)
+ {
+ if (Avx.IsSupported && Vector.Count == 8)
+ {
+ var states = ActiveSet.DynamicsState.Memory;
+ {
+ var m0 = orientation.X.AsVector256();
+ var m1 = orientation.Y.AsVector256();
+ var m2 = orientation.Z.AsVector256();
+ var m3 = orientation.W.AsVector256();
+ var m4 = position.X.AsVector256();
+ var m5 = position.Y.AsVector256();
+ var m6 = position.Z.AsVector256();
+
+ var n0 = Avx.UnpackLow(m0, m1);
+ var n1 = Avx.UnpackLow(m2, m3);
+ var n2 = Avx.UnpackLow(m4, m5);
+ var n3 = Avx.UnpackLow(m6, m6); //Laze alert.
+ var n4 = Avx.UnpackHigh(m0, m1);
+ var n5 = Avx.UnpackHigh(m2, m3);
+ var n6 = Avx.UnpackHigh(m4, m5);
+ var n7 = Avx.UnpackHigh(m6, m6); //Laze alert.
+
+ var o0 = Avx.Shuffle(n0, n1, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o1 = Avx.Shuffle(n2, n3, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o2 = Avx.Shuffle(n4, n5, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o3 = Avx.Shuffle(n6, n7, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o4 = Avx.Shuffle(n0, n1, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o5 = Avx.Shuffle(n2, n3, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o6 = Avx.Shuffle(n4, n5, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o7 = Avx.Shuffle(n6, n7, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+
+ if (mask[0] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[0]), Avx.Permute2x128(o0, o1, 0 | (2 << 4)));
+ if (mask[1] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[1]), Avx.Permute2x128(o4, o5, 0 | (2 << 4)));
+ if (mask[2] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[2]), Avx.Permute2x128(o2, o3, 0 | (2 << 4)));
+ if (mask[3] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[3]), Avx.Permute2x128(o6, o7, 0 | (2 << 4)));
+ if (mask[4] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[4]), Avx.Permute2x128(o0, o1, 1 | (3 << 4)));
+ if (mask[5] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[5]), Avx.Permute2x128(o4, o5, 1 | (3 << 4)));
+ if (mask[6] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[6]), Avx.Permute2x128(o2, o3, 1 | (3 << 4)));
+ if (mask[7] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[7]), Avx.Permute2x128(o6, o7, 1 | (3 << 4)));
+
+ //if (maskPointer[0] != 0) { states[indices[0]].Motion.Pose.Position.Validate(); states[indices[0]].Motion.Pose.Orientation.Validate(); }
+ //if (maskPointer[1] != 0) { states[indices[1]].Motion.Pose.Position.Validate(); states[indices[1]].Motion.Pose.Orientation.Validate(); }
+ //if (maskPointer[2] != 0) { states[indices[2]].Motion.Pose.Position.Validate(); states[indices[2]].Motion.Pose.Orientation.Validate(); }
+ //if (maskPointer[3] != 0) { states[indices[3]].Motion.Pose.Position.Validate(); states[indices[3]].Motion.Pose.Orientation.Validate(); }
+ //if (maskPointer[4] != 0) { states[indices[4]].Motion.Pose.Position.Validate(); states[indices[4]].Motion.Pose.Orientation.Validate(); }
+ //if (maskPointer[5] != 0) { states[indices[5]].Motion.Pose.Position.Validate(); states[indices[5]].Motion.Pose.Orientation.Validate(); }
+ //if (maskPointer[6] != 0) { states[indices[6]].Motion.Pose.Position.Validate(); states[indices[6]].Motion.Pose.Orientation.Validate(); }
+ //if (maskPointer[7] != 0) { states[indices[7]].Motion.Pose.Position.Validate(); states[indices[7]].Motion.Pose.Orientation.Validate(); }
+ }
+ }
+ else
+ {
+ for (int innerIndex = 0; innerIndex < Vector.Count; ++innerIndex)
+ {
+ if (mask[innerIndex] == 0)
+ continue;
+ ref var pose = ref ActiveSet.DynamicsState[encodedBodyIndices[innerIndex]].Motion.Pose;
+ pose.Position = new Vector3(position.X[innerIndex], position.Y[innerIndex], position.Z[innerIndex]);
+ pose.Orientation = new Quaternion(orientation.X[innerIndex], orientation.Y[innerIndex], orientation.Z[innerIndex], orientation.W[innerIndex]);
+
+ }
+ }
+
+ }
+
+
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ScatterInertia(
+ ref BodyInertiaWide inertia, Vector encodedBodyIndices, Vector mask)
+ {
+ if (Avx.IsSupported && Vector.Count == 8)
+ {
+ var states = ActiveSet.DynamicsState.Memory;
+ {
+ var m0 = inertia.InverseInertiaTensor.XX.AsVector256();
+ var m1 = inertia.InverseInertiaTensor.YX.AsVector256();
+ var m2 = inertia.InverseInertiaTensor.YY.AsVector256();
+ var m3 = inertia.InverseInertiaTensor.ZX.AsVector256();
+ var m4 = inertia.InverseInertiaTensor.ZY.AsVector256();
+ var m5 = inertia.InverseInertiaTensor.ZZ.AsVector256();
+ var m6 = inertia.InverseMass.AsVector256();
+
+ var n0 = Avx.UnpackLow(m0, m1);
+ var n1 = Avx.UnpackLow(m2, m3);
+ var n2 = Avx.UnpackLow(m4, m5);
+ var n3 = Avx.UnpackLow(m6, m6); //Laze alert.
+ var n4 = Avx.UnpackHigh(m0, m1);
+ var n5 = Avx.UnpackHigh(m2, m3);
+ var n6 = Avx.UnpackHigh(m4, m5);
+ var n7 = Avx.UnpackHigh(m6, m6); //Laze alert.
+
+ var o0 = Avx.Shuffle(n0, n1, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o1 = Avx.Shuffle(n2, n3, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o2 = Avx.Shuffle(n4, n5, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o3 = Avx.Shuffle(n6, n7, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o4 = Avx.Shuffle(n0, n1, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o5 = Avx.Shuffle(n2, n3, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o6 = Avx.Shuffle(n4, n5, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o7 = Avx.Shuffle(n6, n7, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+
+ //Note the offset; we're scattering into the world inertias.
+ if (mask[0] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[0]) + 24, Avx.Permute2x128(o0, o1, 0 | (2 << 4)));
+ if (mask[1] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[1]) + 24, Avx.Permute2x128(o4, o5, 0 | (2 << 4)));
+ if (mask[2] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[2]) + 24, Avx.Permute2x128(o2, o3, 0 | (2 << 4)));
+ if (mask[3] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[3]) + 24, Avx.Permute2x128(o6, o7, 0 | (2 << 4)));
+ if (mask[4] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[4]) + 24, Avx.Permute2x128(o0, o1, 1 | (3 << 4)));
+ if (mask[5] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[5]) + 24, Avx.Permute2x128(o4, o5, 1 | (3 << 4)));
+ if (mask[6] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[6]) + 24, Avx.Permute2x128(o2, o3, 1 | (3 << 4)));
+ if (mask[7] != 0) Avx.StoreAligned((float*)(states + encodedBodyIndices[7]) + 24, Avx.Permute2x128(o6, o7, 1 | (3 << 4)));
+
+ //if (maskPointer[0] != 0) { states[indices[0]].Inertia.Local.InverseInertiaTensor.Validate(); states[indices[0]].Inertia.Local.InverseMass.Validate(); }
+ //if (maskPointer[1] != 0) { states[indices[1]].Inertia.Local.InverseInertiaTensor.Validate(); states[indices[1]].Inertia.Local.InverseMass.Validate(); }
+ //if (maskPointer[2] != 0) { states[indices[2]].Inertia.Local.InverseInertiaTensor.Validate(); states[indices[2]].Inertia.Local.InverseMass.Validate(); }
+ //if (maskPointer[3] != 0) { states[indices[3]].Inertia.Local.InverseInertiaTensor.Validate(); states[indices[3]].Inertia.Local.InverseMass.Validate(); }
+ //if (maskPointer[4] != 0) { states[indices[4]].Inertia.Local.InverseInertiaTensor.Validate(); states[indices[4]].Inertia.Local.InverseMass.Validate(); }
+ //if (maskPointer[5] != 0) { states[indices[5]].Inertia.Local.InverseInertiaTensor.Validate(); states[indices[5]].Inertia.Local.InverseMass.Validate(); }
+ //if (maskPointer[6] != 0) { states[indices[6]].Inertia.Local.InverseInertiaTensor.Validate(); states[indices[6]].Inertia.Local.InverseMass.Validate(); }
+ //if (maskPointer[7] != 0) { states[indices[7]].Inertia.Local.InverseInertiaTensor.Validate(); states[indices[7]].Inertia.Local.InverseMass.Validate(); }
+ }
+ }
+ else
+ {
+ for (int innerIndex = 0; innerIndex < Vector.Count; ++innerIndex)
+ {
+ if (mask[innerIndex] == 0)
+ continue;
+ ref var target = ref ActiveSet.DynamicsState[encodedBodyIndices[innerIndex]].Inertia.World;
+ target.InverseInertiaTensor.XX = inertia.InverseInertiaTensor.XX[innerIndex];
+ target.InverseInertiaTensor.YX = inertia.InverseInertiaTensor.YX[innerIndex];
+ target.InverseInertiaTensor.YY = inertia.InverseInertiaTensor.YY[innerIndex];
+ target.InverseInertiaTensor.ZX = inertia.InverseInertiaTensor.ZX[innerIndex];
+ target.InverseInertiaTensor.ZY = inertia.InverseInertiaTensor.ZY[innerIndex];
+ target.InverseInertiaTensor.ZZ = inertia.InverseInertiaTensor.ZZ[innerIndex];
+ target.InverseMass = inertia.InverseMass[innerIndex];
+ }
+ }
+ }
+
+
+ //[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public unsafe void ScatterVelocities(ref BodyVelocityWide sourceVelocities, ref Vector encodedBodyIndices) where TAccessFilter : unmanaged, IBodyAccessFilter
+ {
+ if (Avx.IsSupported && Vector.Count == 8)
+ {
+ //TODO: High precision poses means we'll end up with 64 bits of the second lane containing either orientation components or a position component.
+ //That'll require a revamp of this approach to mask out any writes to the pose components, but it'll all be compile time conditional.
+ Unsafe.SkipInit(out TAccessFilter filter);
+
+ if (filter.AccessLinearVelocity ^ filter.AccessAngularVelocity)
+ {
+ //for (int i = 0; i < 8; ++i)
+ //{
+ // Get(ref sourceVelocities.Linear.X, i) = i + 100;
+ // Get(ref sourceVelocities.Linear.Y, i) = i + 200;
+ // Get(ref sourceVelocities.Linear.Z, i) = i + 300;
+ // Get(ref sourceVelocities.Angular.X, i) = i + 500;
+ // Get(ref sourceVelocities.Angular.Y, i) = i + 600;
+ // Get(ref sourceVelocities.Angular.Z, i) = i + 700;
+ //}
+ //We can't write the entire lane if we only want linear or angular velocity; that would overwrite existing values with invalid data.
+ Vector256 m0, m1, m2;
+ int targetOffset;
+ if (filter.AccessLinearVelocity)
+ {
+ targetOffset = 8;
+ m0 = sourceVelocities.Linear.X.AsVector256();
+ m1 = sourceVelocities.Linear.Y.AsVector256();
+ m2 = sourceVelocities.Linear.Z.AsVector256();
+ }
+ else
+ {
+ targetOffset = 12;
+ m0 = sourceVelocities.Angular.X.AsVector256();
+ m1 = sourceVelocities.Angular.Y.AsVector256();
+ m2 = sourceVelocities.Angular.Z.AsVector256();
+ }
+ //We're being a bit lazy here- you could reduce the instructions a bit more given the two empty source lanes.
+ var n0 = Avx.UnpackLow(m0, m1);
+ var n1 = Avx.UnpackLow(m2, m2);
+ var n4 = Avx.UnpackHigh(m0, m1);
+ var n5 = Avx.UnpackHigh(m2, m2);
+
+ var o0 = Avx.Shuffle(n0, n1, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o2 = Avx.Shuffle(n4, n5, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o4 = Avx.Shuffle(n0, n1, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o6 = Avx.Shuffle(n4, n5, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+
+ var indices = (uint*)Unsafe.AsPointer(ref encodedBodyIndices);
+ var states = ActiveSet.DynamicsState.Memory;
+ if (indices[0] < DynamicLimit) Sse.StoreAligned((float*)(states + indices[0]) + targetOffset, o0.GetLower());
+ if (indices[1] < DynamicLimit) Sse.StoreAligned((float*)(states + indices[1]) + targetOffset, o4.GetLower());
+ if (indices[2] < DynamicLimit) Sse.StoreAligned((float*)(states + indices[2]) + targetOffset, o2.GetLower());
+ if (indices[3] < DynamicLimit) Sse.StoreAligned((float*)(states + indices[3]) + targetOffset, o6.GetLower());
+ if (indices[4] < DynamicLimit) Sse.StoreAligned((float*)(states + indices[4]) + targetOffset, o0.GetUpper());
+ if (indices[5] < DynamicLimit) Sse.StoreAligned((float*)(states + indices[5]) + targetOffset, o4.GetUpper());
+ if (indices[6] < DynamicLimit) Sse.StoreAligned((float*)(states + indices[6]) + targetOffset, o2.GetUpper());
+ if (indices[7] < DynamicLimit) Sse.StoreAligned((float*)(states + indices[7]) + targetOffset, o6.GetUpper());
+
+ }
+ else
+ {
+ //Just like the gather but... transposed, we transpose from the wide representation into per-body velocities.
+ //The motion state struct puts the velocities into the second 32 byte chunk, so we can do a single write per body.
+ var m0 = sourceVelocities.Linear.X.AsVector256();
+ var m1 = sourceVelocities.Linear.Y.AsVector256();
+ var m2 = sourceVelocities.Linear.Z.AsVector256();
+ var m4 = sourceVelocities.Angular.X.AsVector256();
+ var m5 = sourceVelocities.Angular.Y.AsVector256();
+ var m6 = sourceVelocities.Angular.Z.AsVector256();
+
+ //We're being a bit lazy here- you could reduce the instructions a bit more given the two empty source lanes.
+ var n0 = Avx.UnpackLow(m0, m1);
+ var n1 = Avx.UnpackLow(m2, m2);
+ var n2 = Avx.UnpackLow(m4, m5);
+ var n3 = Avx.UnpackLow(m6, m6);
+ var n4 = Avx.UnpackHigh(m0, m1);
+ var n5 = Avx.UnpackHigh(m2, m2);
+ var n6 = Avx.UnpackHigh(m4, m5);
+ var n7 = Avx.UnpackHigh(m6, m6);
+
+ var o0 = Avx.Shuffle(n0, n1, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o1 = Avx.Shuffle(n2, n3, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o2 = Avx.Shuffle(n4, n5, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o3 = Avx.Shuffle(n6, n7, 0 | (1 << 2) | (0 << 4) | (1 << 6));
+ var o4 = Avx.Shuffle(n0, n1, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o5 = Avx.Shuffle(n2, n3, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o6 = Avx.Shuffle(n4, n5, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+ var o7 = Avx.Shuffle(n6, n7, 2 | (3 << 2) | (2 << 4) | (3 << 6));
+
+ var indices = (uint*)Unsafe.AsPointer(ref encodedBodyIndices);
+ var states = ActiveSet.DynamicsState.Memory;
+ if (indices[0] < DynamicLimit) Avx.StoreAligned((float*)(states + indices[0]) + 8, Avx.Permute2x128(o0, o1, 0 | (2 << 4)));
+ if (indices[1] < DynamicLimit) Avx.StoreAligned((float*)(states + indices[1]) + 8, Avx.Permute2x128(o4, o5, 0 | (2 << 4)));
+ if (indices[2] < DynamicLimit) Avx.StoreAligned((float*)(states + indices[2]) + 8, Avx.Permute2x128(o2, o3, 0 | (2 << 4)));
+ if (indices[3] < DynamicLimit) Avx.StoreAligned((float*)(states + indices[3]) + 8, Avx.Permute2x128(o6, o7, 0 | (2 << 4)));
+ if (indices[4] < DynamicLimit) Avx.StoreAligned((float*)(states + indices[4]) + 8, Avx.Permute2x128(o0, o1, 1 | (3 << 4)));
+ if (indices[5] < DynamicLimit) Avx.StoreAligned((float*)(states + indices[5]) + 8, Avx.Permute2x128(o4, o5, 1 | (3 << 4)));
+ if (indices[6] < DynamicLimit) Avx.StoreAligned((float*)(states + indices[6]) + 8, Avx.Permute2x128(o2, o3, 1 | (3 << 4)));
+ if (indices[7] < DynamicLimit) Avx.StoreAligned((float*)(states + indices[7]) + 8, Avx.Permute2x128(o6, o7, 1 | (3 << 4)));
+ }
+
+ //{
+ // var indices = (int*)Unsafe.AsPointer(ref references);
+ // var states = ActiveSet.SolverStates.Memory;
+ // if (indices[0] >= 0) { states[indices[0]].Motion.Velocity.Linear.Validate(); states[indices[0]].Motion.Velocity.Angular.Validate(); }
+ // if (indices[1] >= 0) { states[indices[1]].Motion.Velocity.Linear.Validate(); states[indices[1]].Motion.Velocity.Angular.Validate(); }
+ // if (indices[2] >= 0) { states[indices[2]].Motion.Velocity.Linear.Validate(); states[indices[2]].Motion.Velocity.Angular.Validate(); }
+ // if (indices[3] >= 0) { states[indices[3]].Motion.Velocity.Linear.Validate(); states[indices[3]].Motion.Velocity.Angular.Validate(); }
+ // if (indices[4] >= 0) { states[indices[4]].Motion.Velocity.Linear.Validate(); states[indices[4]].Motion.Velocity.Angular.Validate(); }
+ // if (indices[5] >= 0) { states[indices[5]].Motion.Velocity.Linear.Validate(); states[indices[5]].Motion.Velocity.Angular.Validate(); }
+ // if (indices[6] >= 0) { states[indices[6]].Motion.Velocity.Linear.Validate(); states[indices[6]].Motion.Velocity.Angular.Validate(); }
+ // if (indices[7] >= 0) { states[indices[7]].Motion.Velocity.Linear.Validate(); states[indices[7]].Motion.Velocity.Angular.Validate(); }
+ //}
+ }
+ else
+ {
+ var indices = (uint*)Unsafe.AsPointer(ref encodedBodyIndices);
+ for (int innerIndex = 0; innerIndex < Vector.Count; ++innerIndex)
+ {
+ if (indices[innerIndex] >= DynamicLimit)
+ continue;
+ ref var sourceSlot = ref GetOffsetInstance(ref sourceVelocities, innerIndex);
+ ref var target = ref ActiveSet.DynamicsState[indices[innerIndex]].Motion.Velocity;
+ target.Linear = new Vector3(sourceSlot.Linear.X[0], sourceSlot.Linear.Y[0], sourceSlot.Linear.Z[0]);
+ target.Angular = new Vector3(sourceSlot.Angular.X[0], sourceSlot.Angular.Y[0], sourceSlot.Angular.Z[0]);
+ }
+ }
+ }
+ }
+}
diff --git a/BepuPhysics/BodyDescription.cs b/BepuPhysics/BodyDescription.cs
index 73d57652d..5c97d6e4d 100644
--- a/BepuPhysics/BodyDescription.cs
+++ b/BepuPhysics/BodyDescription.cs
@@ -1,12 +1,12 @@
using BepuPhysics.Collidables;
-using BepuPhysics.Constraints;
-using BepuUtilities;
-using BepuUtilities.Memory;
-using System.Numerics;
-using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace BepuPhysics
{
+ ///
+ /// Describes the thresholds for a body going to sleep.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
public struct BodyActivityDescription
{
///
@@ -30,14 +30,43 @@ public BodyActivityDescription(float sleepThreshold, byte minimumTimestepCountUn
SleepThreshold = sleepThreshold;
MinimumTimestepCountUnderThreshold = minimumTimestepCountUnderThreshold;
}
+
+ ///
+ /// Creates a body activity description. Uses a of 32.
+ ///
+ /// Threshold of squared velocity under which the body is allowed to go to sleep. This is compared against dot(linearVelocity, linearVelocity) + dot(angularVelocity, angularVelocity).
+ /// Note that the body is not guaranteed to go to sleep immediately after meeting this minimum.
+ public static implicit operator BodyActivityDescription(float sleepThreshold)
+ {
+ return new BodyActivityDescription(sleepThreshold);
+ }
}
+ ///
+ /// Describes a body's state.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
public struct BodyDescription
{
+ ///
+ /// Position and orientation of the body.
+ ///
public RigidPose Pose;
- public BodyInertia LocalInertia;
+ ///
+ /// Linear and angular velocity of the body.
+ ///
public BodyVelocity Velocity;
+ ///
+ /// Mass and inertia tensor of the body.
+ ///
+ public BodyInertia LocalInertia;
+ ///
+ /// Shape and collision detection settings for the body.
+ ///
public CollidableDescription Collidable;
+ ///
+ /// Sleeping settings for the body.
+ ///
public BodyActivityDescription Activity;
//Convex shape helpers.
@@ -79,7 +108,7 @@ public static BodyActivityDescription GetDefaultActivity(in TShape shape
/// Collidable to associate with the body.
/// Activity settings for the body.
/// Constructed description for the body.
- public static BodyDescription CreateDynamic(in RigidPose pose, in BodyVelocity velocity, in BodyInertia inertia, in CollidableDescription collidable, in BodyActivityDescription activity)
+ public static BodyDescription CreateDynamic(RigidPose pose, BodyVelocity velocity, BodyInertia inertia, CollidableDescription collidable, BodyActivityDescription activity)
{
return new BodyDescription { Pose = pose, Velocity = velocity, LocalInertia = inertia, Activity = activity, Collidable = collidable };
}
@@ -92,38 +121,11 @@ public static BodyDescription CreateDynamic(in RigidPose pose, in BodyVelocity v
/// Collidable to associate with the body.
/// Activity settings for the body.
/// Constructed description for the body.
- public static BodyDescription CreateDynamic(in RigidPose pose, in BodyInertia inertia, in CollidableDescription collidable, in BodyActivityDescription activity)
+ public static BodyDescription CreateDynamic(RigidPose pose, BodyInertia inertia, CollidableDescription collidable, BodyActivityDescription activity)
{
return new BodyDescription { Pose = pose, LocalInertia = inertia, Activity = activity, Collidable = collidable };
}
- ///
- /// Creates a dynamic body description with identity orientation.
- ///
- /// Position of the body.
- /// Initial velocity of the body.
- /// Local inertia of the body.
- /// Collidable to associate with the body.
- /// Activity settings for the body.
- /// Constructed description for the body.
- public static BodyDescription CreateDynamic(in Vector3 position, in BodyVelocity velocity, in BodyInertia inertia, in CollidableDescription collidable, in BodyActivityDescription activity)
- {
- return new BodyDescription { Pose = new RigidPose(position), Velocity = velocity, LocalInertia = inertia, Activity = activity, Collidable = collidable };
- }
-
- ///
- /// Creates a dynamic body description with zero initial velocity and identity orientation.
- ///
- /// Position of the body.
- /// Local inertia of the body.
- /// Collidable to associate with the body.
- /// Activity settings for the body.
- /// Constructed description for the body.
- public static BodyDescription CreateDynamic(in Vector3 position, in BodyInertia inertia, in CollidableDescription collidable, in BodyActivityDescription activity)
- {
- return new BodyDescription { Pose = new RigidPose(position), LocalInertia = inertia, Activity = activity, Collidable = collidable };
- }
-
///
/// Creates a dynamic body description with collidable, inertia, and activity descriptions generated from a convex shape. Adds the shape to the given shape set.
///
@@ -134,38 +136,19 @@ public static BodyDescription CreateDynamic(in Vector3 position, in BodyInertia
/// Shape collection to add the shape to.
/// Shape to add to the shape set and to create the body from.
/// Constructed description for the body.
- public static BodyDescription CreateConvexDynamic(
- in RigidPose pose, in BodyVelocity velocity, float mass, Shapes shapes, in TConvexShape shape)
- where TConvexShape : unmanaged, IConvexShape
+ public static BodyDescription CreateConvexDynamic(RigidPose pose, BodyVelocity velocity, float mass, Shapes shapes, in TConvexShape shape) where TConvexShape : unmanaged, IConvexShape
{
var description = new BodyDescription
{
Pose = pose,
Velocity = velocity,
Activity = GetDefaultActivity(shape),
- Collidable = new CollidableDescription(shapes.Add(shape), GetDefaultSpeculativeMargin(shape))
+ Collidable = shapes.Add(shape)
};
- shape.ComputeInertia(mass, out description.LocalInertia);
+ description.LocalInertia = shape.ComputeInertia(mass);
return description;
}
- ///
- /// Creates a dynamic body description with identity orientation and collidable, inertia, and activity descriptions generated from a convex shape. Adds the shape to the given shape set.
- ///
- /// Type of the shape to create a body for.
- /// Position of the body.
- /// Initial velocity of the body.
- /// Mass of the body. The inertia tensor will be calculated based on this mass and the shape.
- /// Shape collection to add the shape to.
- /// Shape to add to the shape set and to create the body from.
- /// Constructed description for the body.
- public static BodyDescription CreateConvexDynamic(
- in Vector3 position, in BodyVelocity velocity, float mass, Shapes shapes, in TConvexShape shape)
- where TConvexShape : unmanaged, IConvexShape
- {
- return CreateConvexDynamic(new RigidPose(position), velocity, mass, shapes, shape);
- }
-
///
/// Creates a dynamic body description with zero initial velocity and collidable, inertia, and activity descriptions generated from a convex shape. Adds the shape to the given shape set.
///
@@ -175,29 +158,11 @@ public static BodyDescription CreateConvexDynamic(
/// Shape collection to add the shape to.
/// Shape to add to the shape set and to create the body from.
/// Constructed description for the body.
- public static BodyDescription CreateConvexDynamic(
- in RigidPose pose, float mass, Shapes shapes, in TConvexShape shape)
- where TConvexShape : unmanaged, IConvexShape
+ public static BodyDescription CreateConvexDynamic(RigidPose pose, float mass, Shapes shapes, in TConvexShape shape) where TConvexShape : unmanaged, IConvexShape
{
return CreateConvexDynamic(pose, default, mass, shapes, shape);
}
- ///
- /// Creates a dynamic body description with zero initial velocity, identity orientation, and collidable, inertia, and activity descriptions generated from a convex shape. Adds the shape to the given shape set.
- ///
- /// Type of the shape to create a body for.
- /// Position of the body.
- /// Mass of the body. The inertia tensor will be calculated based on this mass and the shape.
- /// Shape collection to add the shape to.
- /// Shape to add to the shape set and to create the body from.
- /// Constructed description for the body.
- public static BodyDescription CreateConvexDynamic(
- in Vector3 position, float mass, Shapes shapes, in TConvexShape shape)
- where TConvexShape : unmanaged, IConvexShape
- {
- return CreateConvexDynamic(new RigidPose(position), default, mass, shapes, shape);
- }
-
///
/// Creates a kinematic body description.
///
@@ -206,7 +171,7 @@ public static BodyDescription CreateConvexDynamic(
/// Collidable to associate with the body.
/// Activity settings for the body.
/// Constructed description for the body.
- public static BodyDescription CreateKinematic(in RigidPose pose, in BodyVelocity velocity, in CollidableDescription collidable, in BodyActivityDescription activity)
+ public static BodyDescription CreateKinematic(RigidPose pose, BodyVelocity velocity, CollidableDescription collidable, BodyActivityDescription activity)
{
return new BodyDescription { Pose = pose, Velocity = velocity, Activity = activity, Collidable = collidable };
}
@@ -218,36 +183,11 @@ public static BodyDescription CreateKinematic(in RigidPose pose, in BodyVelocity
/// Collidable to associate with the body.
/// Activity settings for the body.
/// Constructed description for the body.
- public static BodyDescription CreateKinematic(in RigidPose pose, in CollidableDescription collidable, in BodyActivityDescription activity)
+ public static BodyDescription CreateKinematic(RigidPose pose, CollidableDescription collidable, BodyActivityDescription activity)
{
return new BodyDescription { Pose = pose, Activity = activity, Collidable = collidable };
}
- ///
- /// Creates a kinematic body description with identity orientation.
- ///
- /// Position of the body.
- /// Initial velocity of the body.
- /// Collidable to associate with the body.
- /// Activity settings for the body.
- /// Constructed description for the body.
- public static BodyDescription CreateKinematic(in Vector3 position, in BodyVelocity velocity, in CollidableDescription collidable, in BodyActivityDescription activity)
- {
- return new BodyDescription { Pose = new RigidPose(position), Velocity = velocity, Activity = activity, Collidable = collidable };
- }
-
- ///
- /// Creates a kinematic body description with identity orientation and zero initial velocity.
- ///
- /// Position of the body.
- /// Collidable to associate with the body.
- /// Activity settings for the body.
- /// Constructed description for the body.
- public static BodyDescription CreateKinematic(in Vector3 position, in CollidableDescription collidable, in BodyActivityDescription activity)
- {
- return new BodyDescription { Pose = new RigidPose(position), Activity = activity, Collidable = collidable };
- }
-
///
/// Creates a kinematic body description with collidable and activity descriptions generated from a convex shape. Adds the shape to the given shape set.
///
@@ -257,35 +197,18 @@ public static BodyDescription CreateKinematic(in Vector3 position, in Collidable
/// Shape collection to add the shape to.
/// Shape to add to the shape set and to create the body from.
/// Constructed description for the body.
- public static BodyDescription CreateConvexKinematic(
- in RigidPose pose, in BodyVelocity velocity, Shapes shapes, in TConvexShape shape)
- where TConvexShape : unmanaged, IConvexShape
+ public static BodyDescription CreateConvexKinematic(RigidPose pose, BodyVelocity velocity, Shapes shapes, in TConvexShape shape) where TConvexShape : unmanaged, IConvexShape
{
var description = new BodyDescription
{
Pose = pose,
Velocity = velocity,
Activity = GetDefaultActivity(shape),
- Collidable = new CollidableDescription(shapes.Add(shape), GetDefaultSpeculativeMargin(shape))
+ Collidable = shapes.Add(shape)
};
return description;
}
- ///
- /// Creates a kinematic body description with identity orientation and collidable and activity descriptions generated from a convex shape. Adds the shape to the given shape set.
- ///
- /// Type of the shape to create a body for.
- /// Position of the body.
- /// Initial velocity of the body.
- /// Shape collection to add the shape to.
- /// Shape to add to the shape set and to create the body from.
- /// Constructed description for the body.
- public static BodyDescription CreateConvexKinematic(
- in Vector3 position, in BodyVelocity velocity, Shapes shapes, in TConvexShape shape)
- where TConvexShape : unmanaged, IConvexShape
- {
- return CreateConvexKinematic(new RigidPose(position), velocity, shapes, shape);
- }
///
/// Creates a kinematic body description with zero initial velocity and collidable and activity descriptions generated from a convex shape. Adds the shape to the given shape set.
@@ -295,29 +218,9 @@ public static BodyDescription CreateConvexKinematic(
/// Shape collection to add the shape to.
/// Shape to add to the shape set and to create the body from.
/// Constructed description for the body.
- public static BodyDescription CreateConvexKinematic(
- in RigidPose pose, Shapes shapes, in TConvexShape shape)
- where TConvexShape : unmanaged, IConvexShape
+ public static BodyDescription CreateConvexKinematic(RigidPose pose, Shapes shapes, TConvexShape shape) where TConvexShape : unmanaged, IConvexShape
{
return CreateConvexKinematic(pose, default, shapes, shape);
}
-
- ///
- /// Creates a kinematic body description with zero initial velocity, identity orientation, and collidable and activity descriptions generated from a convex shape. Adds the shape to the given shape set.
- ///
- /// Type of the shape to create a body for.
- /// Position of the body.
- /// Shape collection to add the shape to.
- /// Shape to add to the shape set and to create the body from.
- /// Constructed description for the body.
- public static BodyDescription CreateConvexKinematic(
- in Vector3 position, Shapes shapes, in TConvexShape shape)
- where TConvexShape : unmanaged, IConvexShape
- {
- return CreateConvexKinematic(new RigidPose(position), default, shapes, shape);
- }
-
}
-
-
}
diff --git a/BepuPhysics/BodyLayoutOptimizer.cs b/BepuPhysics/BodyLayoutOptimizer.cs
deleted file mode 100644
index 6390cb5b6..000000000
--- a/BepuPhysics/BodyLayoutOptimizer.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-using System.Runtime.CompilerServices;
-using System;
-using System.Diagnostics;
-using BepuUtilities.Memory;
-using BepuUtilities.Collections;
-using System.Runtime.InteropServices;
-using System.Threading;
-using BepuUtilities;
-using BepuPhysics.Collidables;
-using BepuPhysics.CollisionDetection;
-
-namespace BepuPhysics
-{
- ///
- /// Incrementally changes the layout of a set of bodies to minimize the cache misses associated with the solver and other systems that rely on connection following.
- ///
- public partial class BodyLayoutOptimizer
- {
- Bodies bodies;
- BroadPhase broadPhase;
- Solver solver;
-
- float optimizationFraction;
- ///
- /// Gets or sets the fraction of all bodies to update each frame.
- ///
- public float OptimizationFraction
- {
- get
- {
- return optimizationFraction;
- }
- set
- {
- if (value > 1 || value < 0)
- throw new ArgumentException("Optimization fraction must be a value from 0 to 1.");
- optimizationFraction = value;
- }
- }
-
- public BodyLayoutOptimizer(Bodies bodies, BroadPhase broadPhase, Solver solver, BufferPool pool, float optimizationFraction = 0.005f)
- {
- this.bodies = bodies;
- this.broadPhase = broadPhase;
- this.solver = solver;
- OptimizationFraction = optimizationFraction;
-
- }
-
- public static void SwapBodyLocation(Bodies bodies, Solver solver, int a, int b)
- {
- Debug.Assert(a != b, "Swapping a body with itself isn't meaningful. Whaddeyer doin?");
- //Enumerate the bodies' current set of constraints, changing the reference in each to the new location.
- //Note that references to both bodies must be changed- both bodies moved!
- //This function does not update the actual position of the list in the graph, so we can modify both without worrying about invalidating indices.
- solver.UpdateForBodyMemorySwap(a, b);
-
- //Update the body locations.
- bodies.ActiveSet.Swap(a, b, ref bodies.HandleToLocation);
- //TODO: If the body layout optimizer occurs before or after all other stages, this swap isn't required. If we move it in between other stages though, we need to keep the inertia
- //coherent with the other body properties.
- //Helpers.Swap(ref bodies.Inertias[a], ref bodies.Inertias[b]);
- }
-
- int nextBodyIndex = 0;
-
- struct IncrementalEnumerator : IForEach
- {
- public Bodies bodies;
- public BroadPhase broadPhase;
- public Solver solver;
- public int Index;
- public int TargetIndexStart;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void LoopBody(int connectedBodyIndex)
- {
- ++Index;
- if (Index > 32)
- return;
- //Only pull bodies over that are to the right. This helps limit pointless fighting.
- //With this condition, objects within an island will tend to move towards the position of the leftmost body.
- //Without it, any progress towards island-level convergence could be undone by the next iteration.
- var newLocationIndex = TargetIndexStart + Index;
- if (connectedBodyIndex > newLocationIndex)
- {
- //Note that we update the memory location immediately. This could affect the next loop iteration.
- //But this is fine; the next iteration will load from that modified data and everything will remain consistent.
-
- //TODO: this implementation can almost certainly be improved-
- //this version goes through all the effort of diving into the type batches for references, then does it all again to move stuff around.
- //A hardcoded swapping operation could do both at once, saving a few indirections.
- //It won't be THAT much faster- every single indirection is already cached.
- //Also, before you do that sort of thing, remember how short this stage is.
- //Note that graph.EnumerateConnectedBodies explicitly excludes the body whose constraints we are enumerating,
- //so we don't have to worry about having the rug pulled by this list swap.
- //(Also, !(x > x) for many values of x.)
- SwapBodyLocation(bodies, solver, connectedBodyIndex, newLocationIndex);
- }
- }
- }
- public void IncrementalOptimize()
- {
- //All this does is look for any bodies which are to the right of a given body. If it finds one, it pulls it to be adjacent.
- //This converges at the island level- that is, running this on a static topology of simulation islands will eventually result in
- //the islands being contiguous in memory, and at least some connected bodies being adjacent to each other.
- //However, within the islands, it may continue to unnecessarily swap objects around as bodies 'fight' for ownership.
- //One body doesn't know that another body has already claimed a body as a child, so this can't produce a coherent unique traversal order.
- //(In fact, it won't generally converge even with a single one dimensional chain of bodies.)
-
- //This optimization routine requires much less overhead than other options, like full island traversals. We only request the connections of a single body,
- //and the swap count is limited to the number of connected bodies.
-
- //Don't bother optimizing if no optimizations can be performed. This condition is assumed during worker execution.
- if (bodies.ActiveSet.Count <= 2)
- return;
- int optimizationCount = (int)Math.Max(1, Math.Round(bodies.ActiveSet.Count * optimizationFraction));
- for (int i = 0; i < optimizationCount; ++i)
- {
- //No point trying to optimize the last two bodies. No optimizations are possible.
- if (nextBodyIndex >= bodies.ActiveSet.Count - 2)
- nextBodyIndex = 0;
-
- var enumerator = new IncrementalEnumerator();
- enumerator.bodies = bodies;
- enumerator.broadPhase = broadPhase;
- enumerator.solver = solver;
- enumerator.TargetIndexStart = nextBodyIndex + 1;
- enumerator.Index = 0;
- bodies.EnumerateConnectedBodyIndices(nextBodyIndex, ref enumerator);
-
- ++nextBodyIndex;
- }
-
- }
- }
-}
diff --git a/BepuPhysics/BodyProperties.cs b/BepuPhysics/BodyProperties.cs
index 180ff6936..85d93c495 100644
--- a/BepuPhysics/BodyProperties.cs
+++ b/BepuPhysics/BodyProperties.cs
@@ -1,43 +1,127 @@
-using BepuPhysics.Collidables;
-using BepuPhysics.Constraints;
-using BepuUtilities;
-using BepuUtilities.Memory;
-using System;
+using BepuUtilities;
using System.Numerics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace BepuPhysics
{
+ ///
+ /// Describes the pose and velocity of a body.
+ ///
+ [StructLayout(LayoutKind.Sequential, Size = 64, Pack = 1)]
+ public struct MotionState
+ {
+ internal const int OffsetToOrientationX = 0;
+ internal const int OffsetToOrientationY = 1;
+ internal const int OffsetToOrientationZ = 2;
+ internal const int OffsetToOrientationW = 3;
+ internal const int OffsetToPositionX = 4;
+ internal const int OffsetToPositionY = 5;
+ internal const int OffsetToPositionZ = 6;
+ internal const int OffsetToLinearX = 8;
+ internal const int OffsetToLinearY = 9;
+ internal const int OffsetToLinearZ = 10;
+ internal const int OffsetToAngularX = 12;
+ internal const int OffsetToAngularY = 13;
+ internal const int OffsetToAngularZ = 14;
+
+ ///
+ /// Pose of the body.
+ ///
+ public RigidPose Pose;
+ ///
+ /// Linear and angular velocity of the body.
+ ///
+ public BodyVelocity Velocity;
+
+ ///
+ /// Returns a string representing the MotionState.
+ ///
+ /// String representing the MotionState.
+ public override string ToString()
+ {
+ return $"Pose: {Pose}, Velocity: {Velocity}";
+ }
+
+ }
+
//TODO: It's a little odd that this exists alongside the BepuUtilities.RigidTransform. The original reasoning was that rigid poses may end up having a non-FP32 representation.
//We haven't taken advantage of that, so right now it's pretty much a pure duplicate.
//When/if we take advantage of larger sizes, we'll have to closely analyze every use case of RigidPose to see if we need the higher precision or not.
///
/// Represents a rigid transformation.
///
+ [StructLayout(LayoutKind.Sequential, Size = 32, Pack = 1)]
public struct RigidPose
{
- public Vector3 Position;
//Note that we store a quaternion rather than a matrix3x3. While this often requires some overhead when performing vector transforms or extracting basis vectors,
//systems needing to interact directly with this representation are often terrifically memory bound. Spending the extra ALU time to convert to a basis can actually be faster
//than loading the extra 5 elements needed to express the full 3x3 rotation matrix. Also, it's marginally easier to keep the rotation normalized over time.
//There may be an argument for the matrix variant to ALSO be stored for some bandwidth-unconstrained stages, but don't worry about that until there's a reason to worry about it.
+ ///
+ /// Orientation of the pose.
+ ///
public Quaternion Orientation;
+ ///
+ /// Position of the pose.
+ ///
+ public Vector3 Position;
- public static RigidPose Identity { get; } = new RigidPose(new Vector3());
+ ///
+ /// Returns a pose with a position at (0,0,0) and identity orientation.
+ ///
+ public static RigidPose Identity => new RigidPose(default);
+ ///
+ /// Creates a rigid pose with the given position and orientation.
+ ///
+ /// Position of the pose.
+ /// Orientation of the pose.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public RigidPose(in Vector3 position, in Quaternion orientation)
+ public RigidPose(Vector3 position, Quaternion orientation)
{
Position = position;
Orientation = orientation;
}
+
+ ///
+ /// Creates a rigid pose with the given position and identity orientation.
+ ///
+ /// Position of the pose.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public RigidPose(in Vector3 position)
+ public RigidPose(Vector3 position)
{
Position = position;
Orientation = Quaternion.Identity;
}
+ ///
+ /// Creates a pose by treating a as a position. Orientation is set to identity.
+ ///
+ /// Position to use in the pose.
+ public static implicit operator RigidPose(Vector3 position)
+ {
+ return new RigidPose(position);
+ }
+
+ ///
+ /// Creates a pose by treating a as an orientation in the pose. Position is set to zero.
+ ///
+ /// Orientation to use in the pose.
+ public static implicit operator RigidPose(Quaternion orientation)
+ {
+ return new RigidPose(default, orientation);
+ }
+
+ ///
+ /// Creates a pose from a tuple of a position and orientation.
+ ///
+ /// Position and orientation to use in the pose.
+ public static implicit operator RigidPose((Vector3 position, Quaternion orientation) poseComponents)
+ {
+ return new RigidPose(poseComponents.position, poseComponents.orientation);
+ }
+
///
/// Transforms a vector by the rigid pose: v * pose.Orientation + pose.Position.
///
@@ -45,7 +129,7 @@ public RigidPose(in Vector3 position)
/// Pose to transform the vector with.
/// Transformed vector.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Transform(in Vector3 v, in RigidPose pose, out Vector3 result)
+ public static void Transform(Vector3 v, in RigidPose pose, out Vector3 result)
{
QuaternionEx.TransformWithoutOverlap(v, pose.Orientation, out var rotated);
result = rotated + pose.Position;
@@ -57,7 +141,7 @@ public static void Transform(in Vector3 v, in RigidPose pose, out Vector3 result
/// Pose to invert and transform the vector with.
/// Transformed vector.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void TransformByInverse(in Vector3 v, in RigidPose pose, out Vector3 result)
+ public static void TransformByInverse(Vector3 v, in RigidPose pose, out Vector3 result)
{
var translated = v - pose.Position;
QuaternionEx.Conjugate(pose.Orientation, out var conjugate);
@@ -87,76 +171,218 @@ public static void MultiplyWithoutOverlap(in RigidPose a, in RigidPose b, out Ri
QuaternionEx.Transform(a.Position, b.Orientation, out var rotatedTranslationA);
result.Position = rotatedTranslationA + b.Position;
}
+
+ ///
+ /// Returns a string representing the RigidPose as "Position, Orientation".
+ ///
+ /// String representing the RigidPose.
+ public override string ToString()
+ {
+ return $"{Position}, {Orientation}";
+ }
}
+ ///
+ /// Linear and angular velocity for a body.
+ ///
+ [StructLayout(LayoutKind.Explicit, Size = 32)]
public struct BodyVelocity
{
+ ///
+ /// Linear velocity associated with the body.
+ ///
+ [FieldOffset(0)]
public Vector3 Linear;
+
+ ///
+ /// Angular velocity associated with the body.
+ ///
+ [FieldOffset(16)]
public Vector3 Angular;
+ ///
+ /// Creates a new set of body velocities. Angular velocity is set to zero.
+ ///
+ /// Linear velocity to use for the body.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public BodyVelocity(in Vector3 linear)
+ public BodyVelocity(Vector3 linear)
{
Linear = linear;
Angular = default;
}
+ ///
+ /// Creates a new set of body velocities.
+ ///
+ /// Linear velocity to use for the body.
+ /// Angular velocity to use for the body.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public BodyVelocity(in Vector3 linear, in Vector3 angular)
+ public BodyVelocity(Vector3 linear, Vector3 angular)
{
Linear = linear;
Angular = angular;
}
+
+ ///
+ /// Creates a body velocity by treating a as a linear velocity. Angular velocity is set to zero.
+ ///
+ /// Linear velocity to use in the body velocity.
+ public static implicit operator BodyVelocity(Vector3 linearVelocity)
+ {
+ return new BodyVelocity(linearVelocity);
+ }
+
+ ///
+ /// Creates a body velocity from a tuple of linear and angular velocities..
+ ///
+ /// Velocities to use in the body velocity.
+ public static implicit operator BodyVelocity((Vector3 linearVelocity, Vector3 angularVelocity) velocities)
+ {
+ return new BodyVelocity(velocities.linearVelocity, velocities.angularVelocity);
+ }
+
+ ///
+ /// Returns a string representing the BodyVelocity as "Linear, Angular".
+ ///
+ /// String representing the BodyVelocity.
+ public override string ToString()
+ {
+ return $"{Linear}, {Angular}";
+ }
}
+ ///
+ /// Stores the inertia for a body.
+ ///
+ /// This representation stores the inverse mass and inverse inertia tensor. Most of the high frequency use cases in the engine naturally use the inverse.
+ [StructLayout(LayoutKind.Sequential, Size = 32, Pack = 4)]
public struct BodyInertia
{
+ ///
+ /// Inverse of the body's inertia tensor.
+ ///
public Symmetric3x3 InverseInertiaTensor;
+ ///
+ /// Inverse of the body's mass.
+ ///
public float InverseMass;
+
+ ///
+ /// Returns a string representing the BodyInertia as "InverseMass, InverseInertiaTensor".
+ ///
+ /// String representing the BodyInertia.
+ public override string ToString()
+ {
+ return $"{InverseMass}, {InverseInertiaTensor}";
+ }
}
- public struct RigidPoses
+ ///
+ /// Stores the local and world views of a body's inertia, packed together for efficient access.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct BodyInertias
+ {
+ ///
+ /// Local inertia of the body.
+ ///
+ public BodyInertia Local;
+ ///
+ /// Transformed world inertia of the body. Note that this is only valid between the velocity integration that updates it and the pose integration that follows.
+ /// Outside of that execution window, this should be considered undefined.
+ ///
+ ///
+ /// We cache this here because velocity integration wants both the local and world inertias, and any integration happening within the solver will do so without the benefit of sequential loads.
+ /// In that context, being able to load a single cache line to grab both local and world inertia helps quite a lot.
+ public BodyInertia World;
+
+ ///
+ /// Returns a string representing the BodyInertias.
+ ///
+ /// String representing the BodyInertias.
+ public override string ToString()
+ {
+ return $"Local: {Local}, World: {World}";
+ }
+ }
+
+ ///
+ /// Stores all body information needed by the solver together.
+ ///
+ ///
+ /// With 2.4's revamp of the solver, every solving stage loads pose, velocity, and inertia for every body in each constraint.
+ /// L2 prefetchers often fetch memory in even-odd pairs of cache lines (see https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf#page=162).
+ /// Since L2 is likely pulling in adjacent cache lines when loading either motion state or inertias, they might as well live together in one block.
+ /// Note that this goes along with a change to the buffer pool's default alignment to 128 bytes.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct BodyDynamics
+ {
+ ///
+ /// Pose and velocity information for the body.
+ ///
+ public MotionState Motion;
+ ///
+ /// Inertia information for the body.
+ ///
+ public BodyInertias Inertia;
+
+ ///
+ /// Returns a string representing the BodyDynamics.
+ ///
+ /// String representing the BodyDynamics.
+ public override string ToString()
+ {
+ return $"Motion: {Motion}, Inertia: {Inertia}";
+ }
+ }
+
+
+ public struct RigidPoseWide
{
public Vector3Wide Position;
public QuaternionWide Orientation;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void Broadcast(in RigidPose pose, out RigidPoses poses)
+ public static void Broadcast(in RigidPose pose, out RigidPoseWide poses)
{
Vector3Wide.Broadcast(pose.Position, out poses.Position);
QuaternionWide.Broadcast(pose.Orientation, out poses.Orientation);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void WriteFirst(in RigidPose pose, ref RigidPoses poses)
+ public static void WriteFirst(in RigidPose pose, ref RigidPoseWide poses)
{
Vector3Wide.WriteFirst(pose.Position, ref poses.Position);
QuaternionWide.WriteFirst(pose.Orientation, ref poses.Orientation);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ReadFirst(in RigidPoses poses, out RigidPose pose)
+ public static void ReadFirst(in RigidPoseWide poses, out RigidPose pose)
{
Vector3Wide.ReadFirst(poses.Position, out pose.Position);
QuaternionWide.ReadFirst(poses.Orientation, out pose.Orientation);
}
}
- public struct BodyVelocities
+ public struct BodyVelocityWide
{
public Vector3Wide Linear;
public Vector3Wide Angular;
}
- public struct BodyInertias
+ public struct BodyInertiaWide
{
public Symmetric3x3Wide InverseInertiaTensor;
- //Note that the inverse mass is included in the BodyInertias bundle. InverseMass is rotationally invariant, so it doesn't need to be updated...
+ //Note that the inverse mass is included in the bundle. InverseMass is rotationally invariant, so it doesn't need to be updated...
//But it's included alongside the rotated inertia tensor because to split it out would require that constraint presteps suffer another cache miss when they
//gather the inverse mass in isolation. (From the solver's perspective, inertia/mass gathering is incoherent.)
public Vector InverseMass;
}
+ ///
+ /// Describes how a body sleeps, and its current state with respect to sleeping.
+ ///
public struct BodyActivity
{
///
diff --git a/BepuPhysics/BodyReference.cs b/BepuPhysics/BodyReference.cs
index 2cd84a271..91b204b83 100644
--- a/BepuPhysics/BodyReference.cs
+++ b/BepuPhysics/BodyReference.cs
@@ -1,12 +1,8 @@
using BepuPhysics.Collidables;
using BepuUtilities;
using BepuUtilities.Collections;
-using BepuUtilities.Memory;
-using System;
-using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
-using System.Text;
namespace BepuPhysics
{
@@ -30,6 +26,7 @@ public struct BodyReference
///
/// Handle of the body to refer to.
/// Collection containing the body.
+ /// This is equivalent to and .
public BodyReference(BodyHandle handle, Bodies bodies)
{
Handle = handle;
@@ -98,7 +95,7 @@ public ref BodyVelocity Velocity
get
{
ref var location = ref MemoryLocation;
- return ref Bodies.Sets[location.SetIndex].Velocities[location.Index];
+ return ref Bodies.Sets[location.SetIndex].DynamicsState[location.Index].Motion.Velocity;
}
}
@@ -111,7 +108,33 @@ public ref RigidPose Pose
get
{
ref var location = ref MemoryLocation;
- return ref Bodies.Sets[location.SetIndex].Poses[location.Index];
+ return ref Bodies.Sets[location.SetIndex].DynamicsState[location.Index].Motion.Pose;
+ }
+ }
+
+ ///
+ /// Gets a reference to the body's motion state, including both pose and velocity.
+ ///
+ public ref MotionState MotionState
+ {
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ get
+ {
+ ref var location = ref MemoryLocation;
+ return ref Bodies.Sets[location.SetIndex].DynamicsState[location.Index].Motion;
+ }
+ }
+
+ ///
+ /// Gets a reference to the body's solver-relevant state, including pose, velocity, and inertia.
+ ///
+ public ref BodyDynamics Dynamics
+ {
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ get
+ {
+ ref var location = ref MemoryLocation;
+ return ref Bodies.Sets[location.SetIndex].DynamicsState[location.Index];
}
}
@@ -137,7 +160,7 @@ public ref BodyInertia LocalInertia
get
{
ref var location = ref MemoryLocation;
- return ref Bodies.Sets[location.SetIndex].LocalInertias[location.Index];
+ return ref Bodies.Sets[location.SetIndex].DynamicsState[location.Index].Inertia.Local;
}
}
@@ -182,12 +205,12 @@ public CollidableReference CollidableReference
///
/// Gets whether the body is kinematic, meaning its inverse inertia and mass are all zero.
///
- public bool Kinematic { get { return Bodies.IsKinematic(LocalInertia); } }
+ public bool Kinematic { get { return Bodies.IsKinematicUnsafeGCHole(ref LocalInertia); } }
///
/// Gets whether the body has locked inertia, meaning its inverse inertia tensor is zero.
///
- public bool HasLockedInertia { get { return Bodies.HasLockedInertia(LocalInertia.InverseInertiaTensor); } }
+ public unsafe bool HasLockedInertia { get { return Bodies.HasLockedInertia((Symmetric3x3*)Unsafe.AsPointer(ref LocalInertia.InverseInertiaTensor)); } }
///
/// If the body is dynamic, turns the body kinematic by setting all inverse inertia and mass values to zero and activates it.
@@ -219,9 +242,10 @@ public void ComputeInverseInertia(out Symmetric3x3 inverseInertia)
{
ref var location = ref MemoryLocation;
ref var set = ref Bodies.Sets[MemoryLocation.SetIndex];
- ref var localInertia = ref set.LocalInertias[location.Index];
- ref var pose = ref set.Poses[location.Index];
- PoseIntegration.RotateInverseInertia(localInertia.InverseInertiaTensor, pose.Orientation, out inverseInertia);
+ //Note that inertia.World is ephemeral data packed into the same cache line for the benefit of the solver.
+ //It should not be assumed to contain up to date information outside of the velocity integration to pose integration interval, so this computes world inertia from scratch.
+ ref var state = ref set.DynamicsState[location.Index];
+ PoseIntegration.RotateInverseInertia(state.Inertia.Local.InverseInertiaTensor, state.Motion.Pose.Orientation, out inverseInertia);
}
///
@@ -324,7 +348,7 @@ public void UpdateBounds()
/// Impulse to apply to the body.
/// World space offset from the center of the body to apply the impulse at.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ApplyImpulse(in Vector3 impulse, in Vector3 impulseOffset, ref BodyInertia localInertia, ref RigidPose pose, ref BodyVelocity velocity)
+ public static void ApplyImpulse(Vector3 impulse, Vector3 impulseOffset, ref BodyInertia localInertia, ref RigidPose pose, ref BodyVelocity velocity)
{
PoseIntegration.RotateInverseInertia(localInertia.InverseInertiaTensor, pose.Orientation, out var inverseInertiaTensor);
ApplyLinearImpulse(impulse, localInertia.InverseMass, ref velocity.Linear);
@@ -339,12 +363,10 @@ public static void ApplyImpulse(in Vector3 impulse, in Vector3 impulseOffset, re
/// Impulse to apply to the body.
/// World space offset from the center of the body to apply the impulse at.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ApplyImpulse(in BodySet set, int index, in Vector3 impulse, in Vector3 impulseOffset)
+ public static void ApplyImpulse(in BodySet set, int index, Vector3 impulse, Vector3 impulseOffset)
{
- ref var localInertia = ref set.LocalInertias[index];
- ref var pose = ref set.Poses[index];
- ref var velocity = ref set.Velocities[index];
- ApplyImpulse(impulse, impulseOffset, ref localInertia, ref pose, ref velocity);
+ ref var state = ref set.DynamicsState[index];
+ ApplyImpulse(impulse, impulseOffset, ref state.Inertia.Local, ref state.Motion.Pose, ref state.Motion.Velocity);
}
///
@@ -354,7 +376,7 @@ public static void ApplyImpulse(in BodySet set, int index, in Vector3 impulse, i
/// Inverse inertia tensor to transform the impulse with.
/// Angular velocity to be modified.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ApplyAngularImpulse(in Vector3 angularImpulse, in Symmetric3x3 inverseInertiaTensor, ref Vector3 angularVelocity)
+ public static void ApplyAngularImpulse(Vector3 angularImpulse, in Symmetric3x3 inverseInertiaTensor, ref Vector3 angularVelocity)
{
Symmetric3x3.TransformWithoutOverlap(angularImpulse, inverseInertiaTensor, out var angularVelocityChange);
angularVelocity += angularVelocityChange;
@@ -367,19 +389,19 @@ public static void ApplyAngularImpulse(in Vector3 angularImpulse, in Symmetric3x
/// Inverse mass to transform the impulse with.
/// Linear velocity to be modified.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ApplyLinearImpulse(in Vector3 impulse, float inverseMass, ref Vector3 linearVelocity)
+ public static void ApplyLinearImpulse(Vector3 impulse, float inverseMass, ref Vector3 linearVelocity)
{
linearVelocity += impulse * inverseMass;
}
///
- /// Applies an impulse to a body at the given world space position. Does not modify activity states.
+ /// Applies an impulse to a body at the given world space offset. Does not modify activity states.
///
/// Impulse to apply to the body.
/// World space offset to apply the impulse at.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void ApplyImpulse(in Vector3 impulse, in Vector3 impulseOffset)
+ public void ApplyImpulse(Vector3 impulse, Vector3 impulseOffset)
{
ref var location = ref MemoryLocation;
ApplyImpulse(Bodies.Sets[location.SetIndex], location.Index, impulse, impulseOffset);
@@ -390,20 +412,21 @@ public void ApplyImpulse(in Vector3 impulse, in Vector3 impulseOffset)
///
/// Impulse to apply to the velocity.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void ApplyLinearImpulse(in Vector3 impulse)
+ public void ApplyLinearImpulse(Vector3 impulse)
{
ref var location = ref MemoryLocation;
ref var set = ref Bodies.Sets[location.SetIndex];
- ApplyLinearImpulse(impulse, set.LocalInertias[location.Index].InverseMass, ref set.Velocities[location.Index].Linear);
+ ref var state = ref set.DynamicsState[location.Index];
+ ApplyLinearImpulse(impulse, state.Inertia.Local.InverseMass, ref state.Motion.Velocity.Linear);
}
///
- /// Computes the velocity of an offset point attached to the body.
+ /// Computes the velocity of a world space offset point attached to the body.
///
- /// Offset from the body's center to
+ /// World space offset from the body's center to the point to measure.
/// Effective velocity of the point if it were attached to the body.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GetVelocityForOffset(in Vector3 offset, out Vector3 velocity)
+ public void GetVelocityForOffset(Vector3 offset, out Vector3 velocity)
{
velocity = Velocity.Linear + Vector3.Cross(Velocity.Angular, offset);
}
@@ -413,14 +436,24 @@ public void GetVelocityForOffset(in Vector3 offset, out Vector3 velocity)
///
/// Impulse to apply to the velocity.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void ApplyAngularImpulse(in Vector3 angularImpulse)
+ public void ApplyAngularImpulse(Vector3 angularImpulse)
{
ref var location = ref MemoryLocation;
ref var set = ref Bodies.Sets[location.SetIndex];
- ref var localInertia = ref set.LocalInertias[location.Index];
- ref var pose = ref set.Poses[location.Index];
- PoseIntegration.RotateInverseInertia(localInertia.InverseInertiaTensor, pose.Orientation, out var inverseInertia);
- ApplyAngularImpulse(angularImpulse, inverseInertia, ref set.Velocities[location.Index].Angular);
+ ref var state = ref set.DynamicsState[location.Index];
+ //Note that inertia.World is ephemeral data packed into the same cache line for the benefit of the solver.
+ //It should not be assumed to contain up to date information outside of the velocity integration to pose integration interval, so this computes world inertia from scratch.
+ PoseIntegration.RotateInverseInertia(state.Inertia.Local.InverseInertiaTensor, state.Motion.Pose.Orientation, out var inverseInertia);
+ ApplyAngularImpulse(angularImpulse, inverseInertia, ref state.Motion.Velocity.Angular);
+ }
+
+ ///
+ /// Implicitly converts a to the that the body reference was created from.
+ ///
+ /// Body reference to extract the handle from.
+ public static implicit operator BodyHandle(BodyReference reference)
+ {
+ return reference.Handle;
}
}
}
diff --git a/BepuPhysics/BodySet.cs b/BepuPhysics/BodySet.cs
index 789096f4e..e994eb30d 100644
--- a/BepuPhysics/BodySet.cs
+++ b/BepuPhysics/BodySet.cs
@@ -35,9 +35,10 @@ public struct BodySet
///
public Buffer IndexToHandle;
- public Buffer Poses;
- public Buffer Velocities;
- public Buffer LocalInertias;
+ ///
+ /// Stores all data involved in solving constraints for a body, including pose, velocity, and inertia.
+ ///
+ public Buffer DynamicsState;
///
/// The collidables owned by each body in the set. Speculative margins, continuity settings, and shape indices can be changed directly.
@@ -89,9 +90,7 @@ internal bool RemoveAt(int bodyIndex, out BodyHandle handle, out int movedBodyIn
{
movedBodyIndex = Count;
//Copy the memory state of the last element down.
- Poses[bodyIndex] = Poses[movedBodyIndex];
- Velocities[bodyIndex] = Velocities[movedBodyIndex];
- LocalInertias[bodyIndex] = LocalInertias[movedBodyIndex];
+ DynamicsState[bodyIndex] = DynamicsState[movedBodyIndex];
Activity[bodyIndex] = Activity[movedBodyIndex];
Collidables[bodyIndex] = Collidables[movedBodyIndex];
//Note that the constraint list is NOT disposed before being overwritten.
@@ -126,15 +125,24 @@ internal void ApplyDescriptionByIndex(int index, in BodyDescription description)
description.LocalInertia.InverseInertiaTensor.ZZ * description.LocalInertia.InverseInertiaTensor.ZZ), $"Invalid body inverse inertia tensor: {description.LocalInertia.InverseInertiaTensor}");
Debug.Assert(!MathChecker.IsInvalid(description.LocalInertia.InverseMass) && description.LocalInertia.InverseMass >= 0, $"Invalid body inverse mass: {description.LocalInertia.InverseMass}");
- Poses[index] = description.Pose;
- Velocities[index] = description.Velocity;
- LocalInertias[index] = description.LocalInertia;
+ ref var state = ref DynamicsState[index];
+ state.Motion.Pose = description.Pose;
+ state.Motion.Velocity = description.Velocity;
+ state.Inertia.Local = description.LocalInertia;
+ //Note that the world inertia is only valid in the velocity integration->pose integration interval, so we don't need to initialize it here for dynamics.
+ //Kinematics, though, can have their inertia updates skipped at runtime since the world inverse inertia should always be a bunch of zeroes, so we pre-zero it.
+ state.Inertia.World = default;
ref var collidable = ref Collidables[index];
- collidable.Continuity = description.Collidable.Continuity;
- collidable.SpeculativeMargin = description.Collidable.SpeculativeMargin;
//Note that we change the shape here. If the collidable transitions from shapeless->shapeful or shapeful->shapeless, the broad phase has to be notified
//so that it can create/remove an entry. That's why this function isn't public.
collidable.Shape = description.Collidable.Shape;
+ collidable.Continuity = description.Collidable.Continuity;
+ collidable.MinimumSpeculativeMargin = description.Collidable.MinimumSpeculativeMargin;
+ collidable.MaximumSpeculativeMargin = description.Collidable.MaximumSpeculativeMargin;
+ //To avoid leaking undefined data, initialize the speculative margin to zero.
+ //Under normal circumstances, it should not be possible for any relevant system to see an undefined speculative margin, since PredictBoundingBoxes sets the value.
+ //However, corner cases like a body being added asleep and woken by the narrow phase can mean prediction does not run.
+ collidable.SpeculativeMargin = 0;
ref var activity = ref Activity[index];
activity.SleepThreshold = description.Activity.SleepThreshold;
activity.MinimumTimestepsUnderThreshold = description.Activity.MinimumTimestepCountUnderThreshold;
@@ -144,13 +152,15 @@ internal void ApplyDescriptionByIndex(int index, in BodyDescription description)
public void GetDescription(int index, out BodyDescription description)
{
- description.Pose = Poses[index];
- description.Velocity = Velocities[index];
- description.LocalInertia = LocalInertias[index];
+ ref var state = ref DynamicsState[index];
+ description.Pose = state.Motion.Pose;
+ description.Velocity = state.Motion.Velocity;
+ description.LocalInertia = state.Inertia.Local;
ref var collidable = ref Collidables[index];
- description.Collidable.Continuity = collidable.Continuity;
description.Collidable.Shape = collidable.Shape;
- description.Collidable.SpeculativeMargin = collidable.SpeculativeMargin;
+ description.Collidable.Continuity = collidable.Continuity;
+ description.Collidable.MinimumSpeculativeMargin = collidable.MinimumSpeculativeMargin;
+ description.Collidable.MaximumSpeculativeMargin = collidable.MaximumSpeculativeMargin;
ref var activity = ref Activity[index];
description.Activity.SleepThreshold = activity.SleepThreshold;
description.Activity.MinimumTimestepCountUnderThreshold = activity.MinimumTimestepsUnderThreshold;
@@ -169,8 +179,16 @@ internal void AddConstraint(int bodyIndex, ConstraintHandle constraintHandle, in
constraints.AllocateUnsafely() = constraint;
}
+ ///
+ /// Removes a constraint from a body's constraint list.
+ ///
+ /// Index of the body to remove the constraint reference from.
+ /// Handle of the constraint to remove.
+ /// Minimum constraint capacity to maintain the body's constraint list. The list will automatically downsize as constraints are removed, but its capacity will not go below this threshold.
+ /// Pool to use to resize the constraint list.
+ /// True if the number of constraints remaining attached to the body is 0, false otherwise.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void RemoveConstraintReference(int bodyIndex, ConstraintHandle constraintHandle, int minimumConstraintCapacityPerBody, BufferPool pool)
+ internal bool RemoveConstraintReference(int bodyIndex, ConstraintHandle constraintHandle, int minimumConstraintCapacityPerBody, BufferPool pool)
{
//This uses a linear search. That's fine; bodies will rarely have more than a handful of constraints associated with them.
//Attempting to use something like a hash set for fast removes would just introduce more constant overhead and slow it down on average.
@@ -195,6 +213,7 @@ internal void RemoveConstraintReference(int bodyIndex, ConstraintHandle constrai
//The list can be trimmed down a bit while still holding all existing constraints and obeying the minimum capacity.
list.Resize(targetCapacity, pool);
}
+ return list.Count == 0;
}
public bool BodyIsConstrainedBy(int bodyIndex, ConstraintHandle constraintHandle)
@@ -210,43 +229,21 @@ public bool BodyIsConstrainedBy(int bodyIndex, ConstraintHandle constraintHandle
return false;
}
-
- ///
- /// Swaps the memory of two bodies. Indexed by memory slot, not by handle index.
- ///
- /// Memory slot of the first body to swap.
- /// Memory slot of the second body to swap.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void Swap(int slotA, int slotB, ref Buffer handleToIndex)
- {
- handleToIndex[IndexToHandle[slotA].Value].Index = slotB;
- handleToIndex[IndexToHandle[slotB].Value].Index = slotA;
- Helpers.Swap(ref IndexToHandle[slotA], ref IndexToHandle[slotB]);
- Helpers.Swap(ref Collidables[slotA], ref Collidables[slotB]);
- Helpers.Swap(ref Poses[slotA], ref Poses[slotB]);
- Helpers.Swap(ref Velocities[slotA], ref Velocities[slotB]);
- Helpers.Swap(ref LocalInertias[slotA], ref LocalInertias[slotB]);
- Helpers.Swap(ref Activity[slotA], ref Activity[slotB]);
- Helpers.Swap(ref Constraints[slotA], ref Constraints[slotB]);
- }
-
- internal unsafe void InternalResize(int targetBodyCapacity, BufferPool pool)
+ internal void InternalResize(int targetBodyCapacity, BufferPool pool)
{
Debug.Assert(targetBodyCapacity > 0, "Resize is not meant to be used as Dispose. If you want to return everything to the pool, use Dispose instead.");
//Note that we base the bundle capacities on post-resize capacity of the IndexToHandle array. This simplifies the conditions on allocation, but increases memory use.
//You may want to change this in the future if memory use is concerning.
targetBodyCapacity = BufferPool.GetCapacityForCount(targetBodyCapacity);
- Debug.Assert(Poses.Length != BufferPool.GetCapacityForCount(targetBodyCapacity), "Should not try to use internal resize of the result won't change the size.");
- pool.ResizeToAtLeast(ref Poses, targetBodyCapacity, Count);
- pool.ResizeToAtLeast(ref Velocities, targetBodyCapacity, Count);
- pool.ResizeToAtLeast(ref LocalInertias, targetBodyCapacity, Count);
+ Debug.Assert(DynamicsState.Length != BufferPool.GetCapacityForCount(targetBodyCapacity), "Should not try to use internal resize of the result won't change the size.");
+ pool.ResizeToAtLeast(ref DynamicsState, targetBodyCapacity, Count);
pool.ResizeToAtLeast(ref IndexToHandle, targetBodyCapacity, Count);
pool.ResizeToAtLeast(ref Collidables, targetBodyCapacity, Count);
pool.ResizeToAtLeast(ref Activity, targetBodyCapacity, Count);
pool.ResizeToAtLeast(ref Constraints, targetBodyCapacity, Count);
}
- public unsafe void Clear(BufferPool pool)
+ public void Clear(BufferPool pool)
{
for (int i = 0; i < Count; ++i)
{
@@ -261,9 +258,7 @@ public unsafe void Clear(BufferPool pool)
/// Pool to return the set's top level buffers to.
public void DisposeBuffers(BufferPool pool)
{
- pool.Return(ref Poses);
- pool.Return(ref Velocities);
- pool.Return(ref LocalInertias);
+ pool.Return(ref DynamicsState);
pool.Return(ref IndexToHandle);
pool.Return(ref Collidables);
pool.Return(ref Activity);
diff --git a/BepuPhysics/BoundingBoxHelpers.cs b/BepuPhysics/BoundingBoxHelpers.cs
index 5929164ae..5c5948c6b 100644
--- a/BepuPhysics/BoundingBoxHelpers.cs
+++ b/BepuPhysics/BoundingBoxHelpers.cs
@@ -1,18 +1,16 @@
using BepuPhysics.Collidables;
using BepuUtilities;
using System;
-using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
-using System.Text;
namespace BepuPhysics
{
public static class BoundingBoxHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void GetAngularBoundsExpansion(in Vector angularSpeed, in Vector vectorDt, in Vector maximumRadius,
- in Vector maximumAngularExpansion, out Vector angularExpansion)
+ public static Vector GetAngularBoundsExpansion(Vector angularSpeed, Vector vectorDt, Vector maximumRadius,
+ Vector maximumAngularExpansion)
{
/*
Angular requires a bit more care. Since the goal is to create a tight bound, simply using a v = w * r approximation isn't ideal. A slightly tighter can be found:
@@ -42,13 +40,27 @@ An extra few dozen ALU cycles is unlikely to meaningfully change the execution t
var cosAngleMinusOne = a2 * new Vector(-1f / 2f) + a4 * new Vector(1f / 24f) - a6 * new Vector(1f / 720f);
//Note that it's impossible for angular motion to cause an increase in bounding box size beyond (maximumRadius-minimumRadius) on any given axis.
//That value, or a conservative approximation, is stored as the maximum angular expansion.
- angularExpansion = Vector.Min(maximumAngularExpansion,
+ return Vector.Min(maximumAngularExpansion,
Vector.SquareRoot(new Vector(-2f) * maximumRadius * maximumRadius * cosAngleMinusOne));
}
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void GetBoundsExpansion(in Vector3Wide linearVelocity, in Vector3Wide angularVelocity, float dt,
- in Vector maximumRadius, in Vector maximumAngularExpansion, out Vector3Wide minExpansion, out Vector3Wide maxExpansion)
+ public static void GetBoundsExpansion(Vector3Wide linearVelocity, Vector dtWide, Vector angularExpansion, out Vector3Wide minExpansion, out Vector3Wide maxExpansion)
+ {
+ var linearDisplacement = linearVelocity * dtWide;
+ var zero = Vector.Zero;
+ minExpansion = Vector3Wide.Min(zero, linearDisplacement);
+ maxExpansion = Vector3Wide.Max(zero, linearDisplacement);
+ Vector3Wide.Subtract(minExpansion, angularExpansion, out minExpansion);
+ Vector3Wide.Add(maxExpansion, angularExpansion, out maxExpansion);
+ }
+
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetBoundsExpansion(
+ Vector3Wide linearVelocity, Vector3Wide angularVelocity, Vector dtWide, Vector maximumRadius, Vector maximumAngularExpansion,
+ out Vector3Wide minBoundsExpansion, out Vector3Wide maxBoundsExpansion)
{
/*
If an object sitting on a plane had a raw (unexpanded) AABB that is just barely above the plane, no contacts would be generated.
@@ -93,26 +105,18 @@ There are ways to address this- all of which are a bit expensive- but CCD as imp
Linear is pretty simple- expand the bounding box in the direction of linear displacement (linearVelocity * dt).
*/
- Vector vectorDt = new Vector(dt);
- Vector3Wide.Scale(linearVelocity, vectorDt, out var linearDisplacement);
-
- var zero = Vector.Zero;
- Vector3Wide.Min(zero, linearDisplacement, out minExpansion);
- Vector3Wide.Max(zero, linearDisplacement, out maxExpansion);
Vector3Wide.Length(angularVelocity, out var angularSpeed);
- GetAngularBoundsExpansion(angularSpeed, vectorDt, maximumRadius, maximumAngularExpansion, out var angularExpansion);
- Vector3Wide.Subtract(minExpansion, angularExpansion, out minExpansion);
- Vector3Wide.Add(maxExpansion, angularExpansion, out maxExpansion);
+ var angularExpansion = GetAngularBoundsExpansion(angularSpeed, dtWide, maximumRadius, maximumAngularExpansion);
+ GetBoundsExpansion(linearVelocity, dtWide, angularExpansion, out minBoundsExpansion, out maxBoundsExpansion);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ExpandBoundingBoxes(ref Vector3Wide min, ref Vector3Wide max, ref BodyVelocities velocities, float dt,
- ref Vector maximumRadius, ref Vector maximumAngularExpansion, ref Vector maximumExpansion)
+ public static void ExpandBoundingBoxes(BodyVelocityWide velocities, Vector dtWide, Vector maximumRadius, Vector maximumAngularExpansion, Vector maximumExpansion,
+ ref Vector3Wide min, ref Vector3Wide max)
{
- GetBoundsExpansion(velocities.Linear, velocities.Angular, dt, maximumRadius, maximumAngularExpansion, out var minDisplacement, out var maxDisplacement);
- //The maximum expansion passed into this function is the speculative margin for discrete mode collidables, and ~infinity for passive or continuous ones.
- Vector3Wide.Max(-maximumExpansion, minDisplacement, out minDisplacement);
- Vector3Wide.Min(maximumExpansion, maxDisplacement, out maxDisplacement);
+ GetBoundsExpansion(velocities.Linear, velocities.Angular, dtWide, maximumRadius, maximumAngularExpansion, out var minDisplacement, out var maxDisplacement);
+ minDisplacement = Vector3Wide.Max(-maximumExpansion, minDisplacement);
+ maxDisplacement = Vector3Wide.Min(maximumExpansion, maxDisplacement);
Vector3Wide.Add(min, minDisplacement, out min);
Vector3Wide.Add(max, maxDisplacement, out max);
@@ -121,10 +125,8 @@ public static void ExpandBoundingBoxes(ref Vector3Wide min, ref Vector3Wide max,
//This is simply a internally vectorized version of the above.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void GetAngularBoundsExpansion(in Vector3 angularVelocity, float dt,
- float maximumRadius, float maximumAngularExpansion, out Vector3 expansion)
+ public static float GetAngularBoundsExpansion(float angularVelocityMagnitude, float dt, float maximumRadius, float maximumAngularExpansion)
{
- var angularVelocityMagnitude = angularVelocity.Length();
var a = MathHelper.Min(angularVelocityMagnitude * dt, MathHelper.Pi / 3f);
var a2 = a * a;
var a4 = a2 * a2;
@@ -132,19 +134,29 @@ public static void GetAngularBoundsExpansion(in Vector3 angularVelocity, float d
var cosAngleMinusOne = a2 * (-1f / 2f) + a4 * (1f / 24f) - a6 * (1f / 720f);
//Note that it's impossible for angular motion to cause an increase in bounding box size beyond (maximumRadius-minimumRadius) on any given axis.
//That value, or a conservative approximation, is stored as the maximum angular expansion.
- expansion = new Vector3(MathHelper.Min(maximumAngularExpansion,
- (float)Math.Sqrt(-2f * maximumRadius * maximumRadius * cosAngleMinusOne)));
+ return MathHelper.Min(maximumAngularExpansion, (float)Math.Sqrt(-2f * maximumRadius * maximumRadius * cosAngleMinusOne));
}
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void GetBoundsExpansion(in Vector3 linearVelocity, in Vector3 angularVelocity, float dt,
+ public static void GetBoundsExpansion(Vector3 linearVelocity, float dt, float angularExpansion, out Vector3 minExpansion, out Vector3 maxExpansion)
+ {
+ var linearDisplacement = linearVelocity * dt;
+ var zero = Vector3.Zero;
+ var broadcastExpansion = new Vector3(angularExpansion);
+ minExpansion = Vector3.Min(zero, linearDisplacement) - broadcastExpansion;
+ maxExpansion = Vector3.Max(zero, linearDisplacement) + broadcastExpansion;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetBoundsExpansion(Vector3 linearVelocity, Vector3 angularVelocity, float dt,
float maximumRadius, float maximumAngularExpansion, float maximumAllowedExpansion, out Vector3 minExpansion, out Vector3 maxExpansion)
{
var linearDisplacement = linearVelocity * dt;
Vector3 zero = default;
minExpansion = Vector3.Min(zero, linearDisplacement);
maxExpansion = Vector3.Max(zero, linearDisplacement);
- GetAngularBoundsExpansion(angularVelocity, dt, maximumRadius, maximumAngularExpansion, out var angularExpansion);
+ var angularExpansion = new Vector3(GetAngularBoundsExpansion(angularVelocity.Length(), dt, maximumRadius, maximumAngularExpansion));
var maximumAllowedExpansionBroadcasted = new Vector3(maximumAllowedExpansion);
minExpansion = Vector3.Max(-maximumAllowedExpansionBroadcasted, minExpansion - angularExpansion);
@@ -152,7 +164,7 @@ public static void GetBoundsExpansion(in Vector3 linearVelocity, in Vector3 angu
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ExpandBoundingBox(ref Vector3 min, ref Vector3 max, in Vector3 linearVelocity, in Vector3 angularVelocity, float dt,
+ public static void ExpandBoundingBox(ref Vector3 min, ref Vector3 max, Vector3 linearVelocity, Vector3 angularVelocity, float dt,
float maximumRadius, float maximumAngularExpansion, float maximumAllowedExpansion)
{
GetBoundsExpansion(linearVelocity, angularVelocity, dt, maximumRadius, maximumAngularExpansion, maximumAllowedExpansion, out var minExpansion, out var maxExpansion);
@@ -161,7 +173,7 @@ public static void ExpandBoundingBox(ref Vector3 min, ref Vector3 max, in Vector
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe static void ExpandBoundingBox(in Vector3Wide expansion, ref Vector3Wide min, ref Vector3Wide max)
+ public static void ExpandBoundingBox(in Vector3Wide expansion, ref Vector3Wide min, ref Vector3Wide max)
{
Vector3Wide.Min(Vector.Zero, expansion, out var minExpansion);
Vector3Wide.Max(Vector.Zero, expansion, out var maxExpansion);
@@ -176,11 +188,12 @@ public unsafe static void ExpandBoundingBox(in Vector3Wide expansion, ref Vector
/// Expands the bounding box surrounding a shape A in the local space of some other collidable B.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static unsafe void ExpandLocalBoundingBoxes(ref Vector3Wide min, ref Vector3Wide max,
+ public static void ExpandLocalBoundingBoxes(ref Vector3Wide min, ref Vector3Wide max,
in Vector radiusA, in Vector3Wide localPositionA, in Vector3Wide localRelativeLinearVelocityA, in Vector3Wide angularVelocityA, in Vector3Wide angularVelocityB, float dt,
in Vector maximumRadius, in Vector maximumAngularExpansion, in Vector maximumAllowedExpansion)
{
- GetBoundsExpansion(localRelativeLinearVelocityA, angularVelocityA, dt,
+ var dtWide = new Vector(dt);
+ GetBoundsExpansion(localRelativeLinearVelocityA, angularVelocityA, dtWide,
maximumRadius + radiusA, maximumAngularExpansion + radiusA, out var minExpansion, out var maxExpansion);
Vector3Wide.LengthSquared(angularVelocityB, out var angularSpeedBSquared);
if (Vector.GreaterThanAny(angularSpeedBSquared, Vector.Zero))
@@ -189,7 +202,7 @@ public static unsafe void ExpandLocalBoundingBoxes(ref Vector3Wide min, ref Vect
Vector3Wide.Length(localPositionA, out var radiusB);
Vector3Wide.Length(localRelativeLinearVelocityA, out var linearSpeed);
var worstCaseRadius = linearSpeed * dt + radiusB;
- GetAngularBoundsExpansion(Vector.SquareRoot(angularSpeedBSquared), maximumRadius + worstCaseRadius, maximumAngularExpansion + worstCaseRadius, new Vector(dt), out var angularExpansionB);
+ var angularExpansionB = GetAngularBoundsExpansion(Vector.SquareRoot(angularSpeedBSquared), maximumRadius + worstCaseRadius, maximumAngularExpansion + worstCaseRadius, dtWide);
Vector3Wide.Subtract(minExpansion, angularExpansionB, out minExpansion);
Vector3Wide.Add(maxExpansion, angularExpansionB, out maxExpansion);
}
@@ -204,26 +217,8 @@ public static unsafe void ExpandLocalBoundingBoxes(ref Vector3Wide min, ref Vect
Vector3Wide.Add(max, localPositionA, out max);
}
- ///
- /// Expands a bounding box by an extremely small amount heuristically.
- ///
- /// Maximum radius of the shape to base the expansion on.
- /// Minimum bounds of the shape to expand.
- /// Maximum bounds of the shape to expand.
- /// This exists as a byproduct of the MeshReduction's contact filtering policy. Any contacts generated outside the query bounds are ignored.
- /// While this works okay for the most part, there are cases where a real contact is just *barely* outside the bounds for numerical reasons and gets removed.
- /// In pathological cases that results in a contact with positive depth being removed. Incrementally expanding the bounding boxes avoids this.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void EpsilonExpandLocalBoundingBoxes(Vector maximumRadius, ref Vector3Wide min, ref Vector3Wide max)
- {
- var expansion = maximumRadius * new Vector(1e-4f);
- Vector3Wide.Subtract(min, expansion, out min);
- Vector3Wide.Add(max, expansion, out max);
- }
-
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe static void ExpandBoundingBox(in Vector3 expansion, ref Vector3 min, ref Vector3 max)
+ public static void ExpandBoundingBox(Vector3 expansion, ref Vector3 min, ref Vector3 max)
{
var minExpansion = Vector3.Min(default, expansion);
var maxExpansion = Vector3.Max(default, expansion);
@@ -234,8 +229,8 @@ public unsafe static void ExpandBoundingBox(in Vector3 expansion, ref Vector3 mi
///
/// Computes the bounding box of a child shape A in the local space of some other collidable B with a sweep direction representing the net linear motion.
///
- public static unsafe void GetLocalBoundingBoxForSweep(TypedIndex shapeIndex, Shapes shapes, in RigidPose shapePoseLocalToA, in Quaternion orientationA, in BodyVelocity velocityA,
- in Vector3 offsetB, in Quaternion orientationB, in BodyVelocity velocityB, float dt, out Vector3 sweep, out Vector3 min, out Vector3 max)
+ public static void GetLocalBoundingBoxForSweep(TypedIndex shapeIndex, Shapes shapes, in RigidPose shapePoseLocalToA, Quaternion orientationA, in BodyVelocity velocityA,
+ Vector3 offsetB, Quaternion orientationB, in BodyVelocity velocityB, float dt, out Vector3 sweep, out Vector3 min, out Vector3 max)
{
//TODO: For any significant amount of B angular velocity, the resulting bounding boxes can be enormous in local space.
//You should strongly consider heuristically choosing a world space path. For tree-based compounds, this would require a dedicated slow world space traversal.
@@ -250,12 +245,12 @@ public static unsafe void GetLocalBoundingBoxForSweep(TypedIndex shapeIndex, Sha
shapes[shapeIndex.Type].ComputeBounds(shapeIndex.Index, poseARotatedIntoBLocalSpace.Orientation, out var maximumRadiusA, out var maximumAngularExpansionA, out min, out max);
//Object A could rotate around its center.
var worstCaseRadiusA = shapePoseLocalToA.Position.Length();
- GetAngularBoundsExpansion(velocityA.Angular, dt, worstCaseRadiusA + maximumRadiusA, worstCaseRadiusA + maximumAngularExpansionA, out var angularExpansionA);
+ var angularExpansionA = GetAngularBoundsExpansion(velocityA.Angular.Length(), dt, worstCaseRadiusA + maximumRadiusA, worstCaseRadiusA + maximumAngularExpansionA);
//Rotation of object B could induce an arc in object A.
//The furthest the convex can be from the compound local origin is no further than the sweep pushing it directly away from the compound, while rotation swings A's local pose away.
var worstCaseRadiusB = sweep.Length() + localOffsetB.Length() + worstCaseRadiusA;
- GetAngularBoundsExpansion(velocityB.Angular, dt, worstCaseRadiusB + maximumRadiusA, worstCaseRadiusB + maximumAngularExpansionA, out var angularExpansionB);
- var combinedAngularExpansion = angularExpansionA + angularExpansionB;
+ var angularExpansionB = GetAngularBoundsExpansion(velocityB.Angular.Length(), dt, worstCaseRadiusB + maximumRadiusA, worstCaseRadiusB + maximumAngularExpansionA);
+ var combinedAngularExpansion = new Vector3(angularExpansionA + angularExpansionB);
min = localOriginToA + min - combinedAngularExpansion;
max = localOriginToA + max + combinedAngularExpansion;
@@ -264,8 +259,8 @@ public static unsafe void GetLocalBoundingBoxForSweep(TypedIndex shapeIndex, Sha
///
/// Computes the bounding box of shape A in the local space of some other collidable B with a sweep direction representing the net linear motion.
///
- public static unsafe void GetLocalBoundingBoxForSweep(ref TConvex shape, in Quaternion orientationA, in BodyVelocity velocityA,
- in Vector3 offsetB, in Quaternion orientationB, in BodyVelocity velocityB, float dt, out Vector3 sweep, out Vector3 min, out Vector3 max) where TConvex : struct, IConvexShape
+ public static void GetLocalBoundingBoxForSweep(ref TConvex shape, Quaternion orientationA, in BodyVelocity velocityA,
+ Vector3 offsetB, Quaternion orientationB, in BodyVelocity velocityB, float dt, out Vector3 sweep, out Vector3 min, out Vector3 max) where TConvex : struct, IConvexShape
{
//TODO: For any significant amount of B angular velocity, the resulting bounding boxes can be enormous in local space.
//You should strongly consider heuristically choosing a world space path. For tree-based compounds, this would require a dedicated slow world space traversal.
@@ -276,11 +271,11 @@ public static unsafe void GetLocalBoundingBoxForSweep(ref TConvex shape
QuaternionEx.ConcatenateWithoutOverlap(orientationA, inverseOrientationB, out var localOrientationA);
shape.ComputeAngularExpansionData(out var maximumRadiusA, out var maximumAngularExpansionA);
- BoundingBoxHelpers.GetAngularBoundsExpansion(velocityA.Angular, dt, maximumRadiusA, maximumAngularExpansionA, out var angularExpansionA);
+ var angularExpansionA = GetAngularBoundsExpansion(velocityA.Angular.Length(), dt, maximumRadiusA, maximumAngularExpansionA);
//The furthest the convex can be from the compound is no further than the sweep pushing it directly away from the compound.
var worstCaseRadiusB = sweep.Length() + localOffsetB.Length();
- BoundingBoxHelpers.GetAngularBoundsExpansion(velocityB.Angular, dt, worstCaseRadiusB + maximumRadiusA, worstCaseRadiusB + maximumAngularExpansionA, out var angularExpansionB);
- var combinedAngularExpansion = angularExpansionA + angularExpansionB;
+ var angularExpansionB = GetAngularBoundsExpansion(velocityB.Angular.Length(), dt, worstCaseRadiusB + maximumRadiusA, worstCaseRadiusB + maximumAngularExpansionA);
+ var combinedAngularExpansion = new Vector3(angularExpansionA + angularExpansionB);
shape.ComputeBounds(localOrientationA, out min, out max);
min = min - localOffsetB - combinedAngularExpansion;
diff --git a/BepuPhysics/CollidableProperty.cs b/BepuPhysics/CollidableProperty.cs
index 2d0746d56..62dd41823 100644
--- a/BepuPhysics/CollidableProperty.cs
+++ b/BepuPhysics/CollidableProperty.cs
@@ -1,10 +1,8 @@
using BepuPhysics.Collidables;
using BepuUtilities.Memory;
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
-using System.Text;
namespace BepuPhysics
{
@@ -50,8 +48,7 @@ public CollidableProperty(Simulation simulation, BufferPool pool = null)
///
/// Initializes the property collection if the Bodies/Statics-less constructor was used.
///
- /// Bodies collection to track.
- /// Statics collection to track.
+ /// Simulation whose bodies and statics will be tracked.
public void Initialize(Simulation simulation)
{
if (this.simulation != null)
@@ -94,7 +91,7 @@ public ref T this[StaticHandle staticHandle]
///
/// Gets a reference to the properties associated with a collidable.
///
- /// Collidable to retrieve the properties for.
+ /// Collidable to retrieve the properties for.
/// Reference to properties associated with a collidable.
public ref T this[CollidableReference collidable]
{
@@ -146,7 +143,7 @@ public ref T Allocate(StaticHandle handle)
///
/// Ensures there is space for a given collidable reference and returns a reference to the used memory.
///
- /// Collidable reference to allocate for.
+ /// Collidable reference to allocate for.
/// Reference to the data for the given collidable.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T Allocate(CollidableReference collidableReference)
diff --git a/BepuPhysics/Collidables/BigCompound.cs b/BepuPhysics/Collidables/BigCompound.cs
index a172c3b96..33d044bce 100644
--- a/BepuPhysics/Collidables/BigCompound.cs
+++ b/BepuPhysics/Collidables/BigCompound.cs
@@ -4,15 +4,16 @@
using BepuUtilities.Memory;
using System.Diagnostics;
using BepuUtilities;
-using BepuUtilities.Collections;
using BepuPhysics.Trees;
using BepuPhysics.CollisionDetection.CollisionTasks;
+using System.Runtime.InteropServices;
namespace BepuPhysics.Collidables
{
///
/// Compound shape containing a bunch of shapes accessible through a tree acceleration structure. Useful for compounds with lots of children.
///
+ [StructLayout(LayoutKind.Sequential)]
public struct BigCompound : ICompoundShape
{
///
@@ -24,31 +25,95 @@ public struct BigCompound : ICompoundShape
///
public Buffer Children;
+ ///
+ /// Creates a BigCompound shape instance, but leaves the Tree in an unbuilt state. The Tree must be built before the compound can be used.
+ ///
+ /// Children to use in the compound.
+ /// Pool used to allocate acceleration structures.
+ /// Created compound shape.
+ /// In some cases, the default binned build may not be the ideal builder. This function does everything needed to set up a tree without the expense of figuring out the details of the acceleration structure.
+ /// The user can then run whatever build/refinement process is appropriate.
+ public static BigCompound CreateWithoutTreeBuild(Buffer children, BufferPool pool)
+ {
+ Debug.Assert(children.Length > 0, "Compounds must have a nonzero number of children.");
+ BigCompound compound = default;
+ compound.Children = children;
+ compound.Tree = new Tree(pool, children.Length)
+ {
+ //If this codepath is being used, we're assuming that the children are as given.
+ //so we can go ahead and set the node/leaf counts.
+ //(This is in contrast to creating a tree with a certain capacity, but then relying on incremental adds/removes later.)
+ //Note that the tree still has a root node even if there's one leaf; it's a partial node and requires special handling.
+ NodeCount = int.Max(1, children.Length - 1),
+ LeafCount = children.Length
+ };
+ return compound;
+ }
+
+
+ ///
+ /// Fills a buffer of subtrees according to a buffer of triangles.
+ ///
+ /// The term "subtree" is used because the binned builder does not care whether the input came from leaf nodes or a refinement process's internal nodes.
+ /// Children to build subtrees from.
+ /// Shapes set in which child shapes are allocated.
+ /// Subtrees created for the triangles.
+ public static void FillSubtreesForChildren(Span children, Shapes shapes, Span subtrees)
+ {
+ if (subtrees.Length != children.Length)
+ throw new ArgumentException("Triangles and subtrees span lengths should match.");
+ Debug.Assert(Compound.ValidateChildIndices(children, shapes), "Children must all be convex.");
+ for (int i = 0; i < children.Length; ++i)
+ {
+ ref var t = ref children[i];
+ ref var subtree = ref subtrees[i];
+ Compound.ComputeChildBounds(children[i], Quaternion.Identity, shapes, out subtree.Min, out subtree.Max);
+ subtree.LeafCount = 1;
+ subtree.Index = Tree.Encode(i);
+ }
+ }
+
+ ///
+ /// Creates a compound shape instance and builds an acceleration structure using a sweep builder.
+ ///
+ /// Children to use in the compound.
+ /// Shapes set in which child shapes are allocated.
+ /// Pool used to allocate acceleration structures.
+ /// Created compound shape.
+ /// The sweep builder is significantly slower than the binned builder, but can sometimes create higher quality trees.
+ /// Note that the binned builder can be tuned to create higher quality trees. That is usually a better choice than trying to use the sweep builder; this is here primarily for legacy reasons.
+ public unsafe static BigCompound CreateWithSweepBuild(Buffer children, Shapes shapes, BufferPool pool)
+ {
+ var compound = CreateWithoutTreeBuild(children, pool);
+ pool.Take(children.Length, out var subtrees);
+ FillSubtreesForChildren(children, shapes, subtrees);
+ Debug.Assert(sizeof(BoundingBox) == sizeof(NodeChild),
+ "This assumption *should* hold, because the binned builder relies on it. If it doesn't, something weird as happened." +
+ "Did you forget about this requirement when revamping for 64 bit or something?");
+ //NodeChild intentionally shares the same memory layout as BoundingBox. NodeChild just includes some extra data in the fields unused by bounds.
+ compound.Tree.SweepBuild(pool, subtrees.As());
+ pool.Return(ref subtrees);
+ return compound;
+ }
+
///
/// Creates a compound shape with an acceleration structure.
///
/// Set of children in the compound.
/// Shapes set in which child shapes are allocated.
/// Pool to use to allocate acceleration structures.
+ /// Thread dispatcher to use to multithread the acceleration structure build, if any.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public BigCompound(Buffer children, Shapes shapes, BufferPool pool)
+ public unsafe BigCompound(Buffer children, Shapes shapes, BufferPool pool, IThreadDispatcher dispatcher = null)
{
- Debug.Assert(children.Length > 0, "Compounds must have a nonzero number of children.");
- Debug.Assert(Compound.ValidateChildIndices(ref children, shapes), "Children must all be convex.");
- Children = children;
- Tree = new Tree(pool, children.Length);
- pool.Take(children.Length, out Buffer leafBounds);
- Compound.ComputeChildBounds(Children[0], Quaternion.Identity, shapes, out leafBounds[0].Min, out leafBounds[0].Max);
- for (int i = 1; i < Children.Length; ++i)
- {
- ref var bounds = ref leafBounds[i];
- Compound.ComputeChildBounds(Children[i], Quaternion.Identity, shapes, out bounds.Min, out bounds.Max);
- }
- Tree.SweepBuild(pool, leafBounds);
- pool.Return(ref leafBounds);
+ this = CreateWithoutTreeBuild(children, pool);
+ pool.Take(children.Length, out Buffer subtrees);
+ FillSubtreesForChildren(children, shapes, subtrees);
+ Tree.BinnedBuild(subtrees, pool, dispatcher);
+ pool.Return(ref subtrees);
}
- public void ComputeBounds(in Quaternion orientation, Shapes shapeBatches, out Vector3 min, out Vector3 max)
+ public readonly void ComputeBounds(Quaternion orientation, Shapes shapeBatches, out Vector3 min, out Vector3 max)
{
Compound.ComputeChildBounds(Children[0], orientation, shapeBatches, out min, out max);
for (int i = 1; i < Children.Length; ++i)
@@ -60,9 +125,9 @@ public void ComputeBounds(in Quaternion orientation, Shapes shapeBatches, out Ve
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddChildBoundsToBatcher(ref BoundingBoxBatcher batcher, in RigidPose pose, in BodyVelocity velocity, int bodyIndex)
+ public readonly void AddChildBoundsToBatcher(ref BoundingBoxBatcher batcher, in RigidPose pose, in BodyVelocity velocity, int bodyIndex)
{
- Compound.AddChildBoundsToBatcher(ref Children, ref batcher, pose, velocity, bodyIndex);
+ Compound.AddChildBoundsToBatcher(Children, ref batcher, pose, velocity, bodyIndex);
}
unsafe struct LeafTester : IRayLeafTester where TRayHitHandler : struct, IShapeRayHitHandler
@@ -87,7 +152,7 @@ public LeafTester(in Buffer children, Shapes shapes, in TRayHitHa
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe void TestLeaf(int leafIndex, RayData* rayData, float* maximumT)
+ public void TestLeaf(int leafIndex, RayData* rayData, float* maximumT, BufferPool pool)
{
if (Handler.AllowTest(leafIndex))
{
@@ -95,7 +160,7 @@ public unsafe void TestLeaf(int leafIndex, RayData* rayData, float* maximumT)
CompoundChildShapeTester tester;
tester.T = -1;
tester.Normal = default;
- Shapes[child.ShapeIndex.Type].RayTest(child.ShapeIndex.Index, child.LocalPose, *rayData, ref *maximumT, ref tester);
+ Shapes[child.ShapeIndex.Type].RayTest(child.ShapeIndex.Index, child.AsPose(), *rayData, ref *maximumT, pool, ref tester);
if (tester.T >= 0)
{
Debug.Assert(*maximumT >= tester.T, "Whatever generated this ray hit should have obeyed the current maximumT value.");
@@ -106,18 +171,18 @@ public unsafe void TestLeaf(int leafIndex, RayData* rayData, float* maximumT)
}
}
- public void RayTest(in RigidPose pose, in RayData ray, ref float maximumT, Shapes shapes, ref TRayHitHandler hitHandler) where TRayHitHandler : struct, IShapeRayHitHandler
+ public readonly void RayTest(in RigidPose pose, in RayData ray, ref float maximumT, Shapes shapes, BufferPool pool, ref TRayHitHandler hitHandler) where TRayHitHandler : struct, IShapeRayHitHandler
{
Matrix3x3.CreateFromQuaternion(pose.Orientation, out var orientation);
Matrix3x3.TransformTranspose(ray.Origin - pose.Position, orientation, out var localOrigin);
Matrix3x3.TransformTranspose(ray.Direction, orientation, out var localDirection);
var leafTester = new LeafTester(Children, shapes, hitHandler, orientation, ray);
- Tree.RayCast(localOrigin, localDirection, ref maximumT, ref leafTester);
+ Tree.RayCast(localOrigin, localDirection, ref maximumT, pool, ref leafTester);
//Copy the hitHandler to preserve any mutation.
hitHandler = leafTester.Handler;
}
- public unsafe void RayTest(in RigidPose pose, ref RaySource rays, Shapes shapes, ref TRayHitHandler hitHandler) where TRayHitHandler : struct, IShapeRayHitHandler
+ public readonly unsafe void RayTest(in RigidPose pose, ref RaySource rays, Shapes shapes, BufferPool pool, ref TRayHitHandler hitHandler) where TRayHitHandler : struct, IShapeRayHitHandler
{
//TODO: Note that we dispatch a bunch of scalar tests here. You could be more clever than this- batched tests are possible.
//May be worth creating a different traversal designed for low ray counts- might be able to get some benefit out of a semidynamic packet or something.
@@ -130,30 +195,68 @@ public unsafe void RayTest(in RigidPose pose, ref RaySource rays
leafTester.OriginalRay = *ray;
Matrix3x3.Transform(ray->Origin - pose.Position, inverseOrientation, out var localOrigin);
Matrix3x3.Transform(ray->Direction, inverseOrientation, out var localDirection);
- Tree.RayCast(localOrigin, localDirection, ref *maximumT, ref leafTester);
+ Tree.RayCast(localOrigin, localDirection, ref *maximumT, pool, ref leafTester);
}
//Preserve any mutations.
hitHandler = leafTester.Handler;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapes)
+ public static ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapes)
{
return new CompoundShapeBatch(pool, initialCapacity, shapes);
}
- public int ChildCount
+ public readonly int ChildCount
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get { return Children.Length; }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ref CompoundChild GetChild(int compoundChildIndex)
+ public readonly ref CompoundChild GetChild(int compoundChildIndex)
{
return ref Children[compoundChildIndex];
}
+ ///
+ /// Adds a child to the compound.
+ ///
+ /// Child to add to the compound.
+ /// Pool to use to resize the compound's children buffer if necessary.
+ /// Shapes collection containing the compound's children.
+ /// This function keeps the in a valid state, but significant changes over time may degrade the tree's quality and result in reduced collision/query performance.
+ /// If this happens, consider calling with a refinementIndex that changes with each call (to prioritize different parts of the tree).
+ /// Incrementing a counter with each call would work fine. The ideal frequency of refinement depends on the kind of modifications being made, but it's likely to be rare.
+ public void Add(CompoundChild child, BufferPool pool, Shapes shapes)
+ {
+ pool.Resize(ref Children, Children.Length + 1, Children.Length);
+ Children[^1] = child;
+ shapes.UpdateBounds(child.LocalPosition, child.LocalOrientation, child.ShapeIndex, out var bounds);
+ var childIndex = Tree.Add(bounds, pool);
+ Debug.Assert(childIndex == Children.Length - 1, "Adding to a tree acts like appending to the list; a newly added element should be in the last slot to match our previous child modification.");
+ }
+
+ ///
+ /// Removes a child from the compound by index. The last child is pulled to fill the gap left by the removed child.
+ ///
+ /// Index of the child to remove from the compound.
+ /// Pool to use to resize the compound's children buffer if necessary.
+ /// This function keeps the in a valid state, but significant changes over time may degrade the tree's quality and result in reduced collision/query performance.
+ /// If this happens, consider calling with a refinementIndex that changes with each call (to prioritize different parts of the tree).
+ /// Incrementing a counter with each call would work fine. The ideal frequency of refinement depends on the kind of modifications being made, but it's likely to be rare.
+ public void RemoveAt(int childIndex, BufferPool pool)
+ {
+ var movedChildIndex = Tree.RemoveAt(childIndex);
+ if (movedChildIndex >= 0)
+ {
+ Children[childIndex] = Children[movedChildIndex];
+ Debug.Assert(movedChildIndex == Children.Length - 1, "The child moved by the tree is expected to be the last one; it's pulled forward to fill the gap left by the removed child.");
+ }
+ //Shrinking the buffer takes care of 'removing' the now-empty last slot.
+ pool.Resize(ref Children, Children.Length - 1, Children.Length - 1);
+ }
+
unsafe struct Enumerator : IBreakableForEach where TSubpairOverlaps : ICollisionTaskSubpairOverlaps
{
public BufferPool Pool;
@@ -179,7 +282,7 @@ public unsafe void FindLocalOverlaps(ref Buffer(pair.Container).Tree.GetOverlaps(pair.Min, pair.Max, ref enumerator);
+ Unsafe.AsRef(pair.Container).Tree.GetOverlaps(pair.Min, pair.Max, pool, ref enumerator);
}
}
@@ -193,13 +296,54 @@ public void TestLeaf(int leafIndex, ref float maximumT)
Unsafe.AsRef(Overlaps).Allocate(Pool) = leafIndex;
}
}
- public unsafe void FindLocalOverlaps(in Vector3 min, in Vector3 max, in Vector3 sweep, float maximumT, BufferPool pool, Shapes shapes, void* overlaps)
+ public readonly unsafe void FindLocalOverlaps(Vector3 min, Vector3 max, Vector3 sweep, float maximumT, BufferPool pool, Shapes shapes, void* overlaps)
where TOverlaps : ICollisionTaskSubpairOverlaps
{
SweepLeafTester enumerator;
enumerator.Pool = pool;
enumerator.Overlaps = overlaps;
- Tree.Sweep(min, max, sweep, maximumT, ref enumerator);
+ Tree.Sweep(min, max, sweep, maximumT, pool, ref enumerator);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void FindLocalOverlaps(Vector3 min, Vector3 max, BufferPool pool, Shapes shapes, ref TEnumerator enumerator)
+ where TEnumerator : IBreakableForEach
+ {
+ Tree.GetOverlaps(min, max, pool, ref enumerator);
+ }
+
+ ///
+ /// Computes the inertia of a compound. Does not recenter the child poses.
+ ///
+ /// Masses of the children.
+ /// Shapes collection containing the data for the compound child shapes.
+ /// Inertia of the compound.
+ public readonly BodyInertia ComputeInertia(Span childMasses, Shapes shapes)
+ {
+ return CompoundBuilder.ComputeInertia(Children, childMasses, shapes);
+ }
+
+ ///
+ /// Computes the inertia of a compound. Recenters the child poses around the calculated center of mass.
+ ///
+ /// Shapes collection containing the data for the compound child shapes.
+ /// Masses of the children.
+ /// Calculated center of mass of the compound. Subtracted from all the compound child poses.
+ /// Inertia of the compound.
+ public readonly BodyInertia ComputeInertia(Span childMasses, Shapes shapes, out Vector3 centerOfMass)
+ {
+ var bodyInertia = CompoundBuilder.ComputeInertia(Children, childMasses, shapes, out centerOfMass);
+ //Recentering moves the children around, so the tree needs to be updated.
+ //Scanning through and explicitly shifting the nodes is slightly more efficient than updating leaf bounds and refitting.
+ for (int i = 0; i < Tree.NodeCount; ++i)
+ {
+ ref var node = ref Tree.Nodes[i];
+ node.A.Min -= centerOfMass;
+ node.A.Max -= centerOfMass;
+ node.B.Min -= centerOfMass;
+ node.B.Max -= centerOfMass;
+ }
+ return bodyInertia;
}
public void Dispose(BufferPool bufferPool)
@@ -212,7 +356,7 @@ public void Dispose(BufferPool bufferPool)
/// Type id of compound shapes.
///
public const int Id = 7;
- public int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
+ public static int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
}
diff --git a/BepuPhysics/Collidables/BoundingBoxBatcher.cs b/BepuPhysics/Collidables/BoundingBoxBatcher.cs
index a9b11ee69..80d5ec0df 100644
--- a/BepuPhysics/Collidables/BoundingBoxBatcher.cs
+++ b/BepuPhysics/Collidables/BoundingBoxBatcher.cs
@@ -1,14 +1,11 @@
using BepuPhysics.Collidables;
using BepuPhysics.CollisionDetection;
using BepuUtilities;
-using BepuUtilities.Collections;
using BepuUtilities.Memory;
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
-using System.Text;
namespace BepuPhysics
{
@@ -69,18 +66,46 @@ public static BoundsContinuation CreateCompoundChildContinuation(int compoundBod
public struct BoundingBoxInstance
{
- public RigidPose Pose;
- public BodyVelocity Velocities;
public int ShapeIndex;
public BoundsContinuation Continuation;
}
- public struct BoundingBoxInstanceWide where TShape : unmanaged, IShape where TShapeWide : unmanaged, IShapeWide
+ public struct BoundingBoxBatch
{
- public TShapeWide Shape;
- public Vector MaximumExpansion;
- public RigidPoses Pose;
- public BodyVelocities Velocities;
+ public Buffer ShapeIndices;
+ public Buffer Continuations;
+ public Buffer MotionStates;
+ public int Count;
+ public bool Allocated => ShapeIndices.Allocated;
+
+ public BoundingBoxBatch(BufferPool pool, int initialCapacity)
+ {
+ pool.Take(initialCapacity, out ShapeIndices);
+ pool.Take(initialCapacity, out Continuations);
+ pool.Take(initialCapacity, out MotionStates);
+ Count = 0;
+ }
+ internal void Add(int shapeIndex, RigidPose pose, BodyVelocity velocity, BoundsContinuation continuation)
+ {
+ ShapeIndices[Count] = shapeIndex;
+ Continuations[Count] = continuation;
+ ref var motionState = ref MotionStates[Count];
+ motionState.Pose = pose;
+ motionState.Velocity = velocity;
+ Count++;
+ }
+
+ public void Dispose(BufferPool pool)
+ {
+ if (Allocated)
+ {
+ pool.Return(ref ShapeIndices);
+ pool.Return(ref Continuations);
+ pool.Return(ref MotionStates);
+ Count = 0;
+ }
+ }
+
}
public struct BoundingBoxBatcher
@@ -92,7 +117,7 @@ public struct BoundingBoxBatcher
internal float dt;
int minimumBatchIndex, maximumBatchIndex;
- Buffer> batches;
+ Buffer batches;
///
/// The number of bodies to accumulate per type before executing an AABB update. The more bodies per batch, the less virtual overhead and execution divergence.
@@ -100,7 +125,7 @@ public struct BoundingBoxBatcher
///
public const int CollidablesPerFlush = 16;
- public unsafe BoundingBoxBatcher(Bodies bodies, Shapes shapes, BroadPhase broadPhase, BufferPool pool, float dt)
+ public BoundingBoxBatcher(Bodies bodies, Shapes shapes, BroadPhase broadPhase, BufferPool pool, float dt)
{
this.bodies = bodies;
this.shapes = shapes;
@@ -116,68 +141,82 @@ public unsafe BoundingBoxBatcher(Bodies bodies, Shapes shapes, BroadPhase broadP
public unsafe void ExecuteConvexBatch(ConvexShapeBatch shapeBatch) where TShape : unmanaged, IConvexShape where TShapeWide : unmanaged, IShapeWide
{
- var instanceBundle = default(BoundingBoxInstanceWide);
- if (instanceBundle.Shape.InternalAllocationSize > 0) //TODO: Check to make sure the JIT omits the branch.
+ Unsafe.SkipInit(out TShapeWide shapeWide);
+ if (shapeWide.InternalAllocationSize > 0) //TODO: Check to make sure the JIT omits the branch.
{
- var memory = stackalloc byte[instanceBundle.Shape.InternalAllocationSize];
- instanceBundle.Shape.Initialize(new RawBuffer(memory, instanceBundle.Shape.InternalAllocationSize));
+ var memory = stackalloc byte[shapeWide.InternalAllocationSize];
+ shapeWide.Initialize(new Buffer(memory, shapeWide.InternalAllocationSize));
}
ref var batch = ref batches[shapeBatch.TypeId];
- ref var instancesBase = ref batch[0];
+ var shapeIndices = batch.ShapeIndices;
+ var continuations = batch.Continuations;
ref var activeSet = ref bodies.ActiveSet;
-
+ var dtWide = new Vector(dt);
+ Span minimumMarginSpan = stackalloc float[Vector.Count];
+ Span maximumMarginSpan = stackalloc float[Vector.Count];
+ Span allowExpansionBeyondSpeculativeMarginSpan = stackalloc int[Vector.Count];
for (int bundleStartIndex = 0; bundleStartIndex < batch.Count; bundleStartIndex += Vector.Count)
{
int countInBundle = batch.Count - bundleStartIndex;
if (countInBundle > Vector.Count)
countInBundle = Vector.Count;
- ref var bundleInstancesStart = ref Unsafe.Add(ref instancesBase, bundleStartIndex);
+
//Note that doing a gather-scatter to enable vectorized bundle execution isn't worth it for some shape types.
//We're just ignoring that fact for simplicity. Bounding box updates aren't a huge concern overall. That said,
//if you want to optimize this further, the shape batches could choose between vectorized and gatherless scalar implementations on a per-type basis.
+ //TODO: This transposition could be significantly improved with intrinsics, as we did for the Bodies state gather operations.
for (int innerIndex = 0; innerIndex < countInBundle; ++innerIndex)
{
- ref var instance = ref Unsafe.Add(ref bundleInstancesStart, innerIndex);
- ref var targetInstanceSlot = ref GatherScatter.GetOffsetInstance(ref instanceBundle, innerIndex);
- //This property should be a constant value and the JIT has type knowledge, so this branch should optimize out.
- if (instanceBundle.Shape.AllowOffsetMemoryAccess)
- targetInstanceSlot.Shape.WriteFirst(shapeBatch.shapes[instance.ShapeIndex]);
- else
- instanceBundle.Shape.WriteSlot(innerIndex, shapeBatch.shapes[instance.ShapeIndex]);
- Vector3Wide.WriteFirst(instance.Pose.Position, ref targetInstanceSlot.Pose.Position);
- QuaternionWide.WriteFirst(instance.Pose.Orientation, ref targetInstanceSlot.Pose.Orientation);
- Vector3Wide.WriteFirst(instance.Velocities.Linear, ref targetInstanceSlot.Velocities.Linear);
- Vector3Wide.WriteFirst(instance.Velocities.Angular, ref targetInstanceSlot.Velocities.Angular);
- ref var collidable = ref activeSet.Collidables[instance.Continuation.BodyIndex];
- GatherScatter.GetFirst(ref targetInstanceSlot.MaximumExpansion) =
- collidable.Continuity.AllowExpansionBeyondSpeculativeMargin ? float.MaxValue : collidable.SpeculativeMargin;
+ var indexInBatch = bundleStartIndex + innerIndex;
+ var shapeIndex = shapeIndices[indexInBatch];
+ shapeWide.WriteSlot(innerIndex, shapeBatch.shapes[shapeIndex]);
+ ref var collidable = ref activeSet.Collidables[continuations[indexInBatch].BodyIndex];
+ minimumMarginSpan[innerIndex] = collidable.MinimumSpeculativeMargin;
+ maximumMarginSpan[innerIndex] = collidable.MaximumSpeculativeMargin;
+ allowExpansionBeyondSpeculativeMarginSpan[innerIndex] = collidable.Continuity.AllowExpansionBeyondSpeculativeMargin ? -1 : 0;
}
- instanceBundle.Shape.GetBounds(ref instanceBundle.Pose.Orientation, countInBundle, out var maximumRadius, out var maximumAngularExpansion, out var bundleMin, out var bundleMax);
- BoundingBoxHelpers.ExpandBoundingBoxes(ref bundleMin, ref bundleMax, ref instanceBundle.Velocities, dt,
- ref maximumRadius, ref maximumAngularExpansion, ref instanceBundle.MaximumExpansion);
+
+ Bodies.TransposeMotionStates(batch.MotionStates.Slice(bundleStartIndex, countInBundle), out var positions, out var orientations, out var velocities);
+ shapeWide.GetBounds(ref orientations, countInBundle, out var maximumRadius, out var maximumAngularExpansion, out var bundleMin, out var bundleMax);
+ //BoundingBoxBatcher is responsible for updating the bounding box AND speculative margin.
+ //In order to know how much we're allowed to expand the bounding box, we need to know the speculative margin.
+ //It's defined by the velocity of the body, and bounded by the body's minimum and maximum.
+ var angularBoundsExpansion = BoundingBoxHelpers.GetAngularBoundsExpansion(Vector3Wide.Length(velocities.Angular), dtWide, maximumRadius, maximumAngularExpansion);
+ var speculativeMargin = Vector3Wide.Length(velocities.Linear) * dtWide + angularBoundsExpansion;
+
+ var minimumSpeculativeMargin = new Vector(minimumMarginSpan);
+ var maximumSpeculativeMargin = new Vector(maximumMarginSpan);
+ var allowExpansionBeyondSpeculativeMargin = new Vector(allowExpansionBeyondSpeculativeMarginSpan);
+ speculativeMargin = Vector.Max(minimumSpeculativeMargin, Vector.Min(maximumSpeculativeMargin, speculativeMargin));
+ var maximumBoundsExpansion = Vector.ConditionalSelect(allowExpansionBeyondSpeculativeMargin, new Vector(float.MaxValue), speculativeMargin);
+ BoundingBoxHelpers.GetBoundsExpansion(velocities.Linear, dtWide, angularBoundsExpansion, out var minExpansion, out var maxExpansion);
+ minExpansion = Vector3Wide.Max(-maximumBoundsExpansion, minExpansion);
+ maxExpansion = Vector3Wide.Min(maximumBoundsExpansion, maxExpansion);
//TODO: Note that this is an area that must be updated if you change the pose representation.
- Vector3Wide.Add(instanceBundle.Pose.Position, bundleMin, out bundleMin);
- Vector3Wide.Add(instanceBundle.Pose.Position, bundleMax, out bundleMax);
+ bundleMin = positions + (bundleMin + minExpansion);
+ bundleMax = positions + (bundleMax + maxExpansion);
for (int innerIndex = 0; innerIndex < countInBundle; ++innerIndex)
{
- ref var instance = ref Unsafe.Add(ref bundleInstancesStart, innerIndex);
- broadPhase.GetActiveBoundsPointers(activeSet.Collidables[instance.Continuation.BodyIndex].BroadPhaseIndex, out var minPointer, out var maxPointer);
- ref var sourceBundleMin = ref GatherScatter.GetOffsetInstance(ref bundleMin, innerIndex);
- ref var sourceBundleMax = ref GatherScatter.GetOffsetInstance(ref bundleMax, innerIndex);
+ var continuation = continuations[bundleStartIndex + innerIndex];
+ ref var collidable = ref activeSet.Collidables[continuation.BodyIndex];
+ broadPhase.GetActiveBoundsPointers(collidable.BroadPhaseIndex, out var minPointer, out var maxPointer);
+
//Note that we merge with the existing bounding box if the body is compound. This requires compounds to be initialized to (maxvalue, -maxvalue).
//TODO: We bite the bullet on quite a bit of complexity to avoid merging on non-compounds. Could be better overall to simply merge on all bodies. Certainly simpler.
//Worth checking the performance; if it's undetectable, just swap to the simpler version.
- if (instance.Continuation.CompoundChild)
+ if (continuation.CompoundChild)
{
- var min = new Vector3(sourceBundleMin.X[0], sourceBundleMin.Y[0], sourceBundleMin.Z[0]);
- var max = new Vector3(sourceBundleMax.X[0], sourceBundleMax.Y[0], sourceBundleMax.Z[0]);
+ collidable.SpeculativeMargin = MathF.Max(collidable.SpeculativeMargin, speculativeMargin[innerIndex]);
+ var min = new Vector3(bundleMin.X[innerIndex], bundleMin.Y[innerIndex], bundleMin.Z[innerIndex]);
+ var max = new Vector3(bundleMax.X[innerIndex], bundleMax.Y[innerIndex], bundleMax.Z[innerIndex]);
BoundingBox.CreateMerged(*minPointer, *maxPointer, min, max, out *minPointer, out *maxPointer);
}
else
{
- *minPointer = new Vector3(sourceBundleMin.X[0], sourceBundleMin.Y[0], sourceBundleMin.Z[0]);
- *maxPointer = new Vector3(sourceBundleMax.X[0], sourceBundleMax.Y[0], sourceBundleMax.Z[0]);
+ collidable.SpeculativeMargin = speculativeMargin[innerIndex];
+ *minPointer = new Vector3(bundleMin.X[innerIndex], bundleMin.Y[innerIndex], bundleMin.Z[innerIndex]);
+ *maxPointer = new Vector3(bundleMax.X[innerIndex], bundleMax.Y[innerIndex], bundleMax.Z[innerIndex]);
}
}
}
@@ -185,30 +224,44 @@ public unsafe void ExecuteConvexBatch(ConvexShapeBatch(HomogeneousCompoundShapeBatch shapeBatch)
where TShape : unmanaged, IHomogeneousCompoundShape
- where TChildShape : IConvexShape
- where TChildShapeWide : IShapeWide
+ where TChildShape : unmanaged, IConvexShape
+ where TChildShapeWide : unmanaged, IShapeWide
{
ref var batch = ref batches[shapeBatch.TypeId];
ref var activeSet = ref bodies.ActiveSet;
for (int i = 0; i < batch.Count; ++i)
{
- ref var instance = ref batch[i];
- var bodyIndex = instance.Continuation.BodyIndex;
+ var shapeIndex = batch.ShapeIndices[i];
+ ref var motionState = ref batch.MotionStates[i];
+ var bodyIndex = batch.Continuations[i].BodyIndex;
ref var collidable = ref activeSet.Collidables[bodyIndex];
- broadPhase.GetActiveBoundsPointers(collidable.BroadPhaseIndex, out var min, out var max);
- var maximumAllowedExpansion = collidable.Continuity.AllowExpansionBeyondSpeculativeMargin ? float.MaxValue : collidable.SpeculativeMargin;
- shapeBatch[instance.ShapeIndex].ComputeBounds(instance.Pose.Orientation, out *min, out *max);
+ shapeBatch[shapeIndex].ComputeBounds(motionState.Pose.Orientation, out var min, out var max);
//Working on the assumption that dynamic meshes are extremely rare, and that dynamic meshes with extremely high angular velocity are even rarer,
//we're just going to use a simplistic upper bound for angular expansion. This simplifies the mesh bounding box calculation quite a bit (no dot products).
- var absMin = Vector3.Abs(*min);
- var absMax = Vector3.Abs(*max);
+ var absMin = Vector3.Abs(min);
+ var absMax = Vector3.Abs(max);
var maximumRadius = Vector3.Max(absMin, absMax).Length();
var minimumComponents = Vector3.Min(absMin, absMax);
var minimumRadius = MathHelper.Min(minimumComponents.X, MathHelper.Min(minimumComponents.Y, minimumComponents.Z));
- BoundingBoxHelpers.ExpandBoundingBox(ref *min, ref *max, instance.Velocities.Linear, instance.Velocities.Angular, dt, maximumRadius, maximumRadius - minimumRadius, maximumAllowedExpansion);
- *min += instance.Pose.Position;
- *max += instance.Pose.Position;
+ var maximumAngularExpansion = maximumRadius - minimumRadius;
+
+ //BoundingBoxBatcher is responsible for updating the bounding box AND speculative margin.
+ //In order to know how much we're allowed to expand the bounding box, we need to know the speculative margin.
+ //It's defined by the velocity of the body, and bounded by the body's minimum and maximum.
+ var angularBoundsExpansion = BoundingBoxHelpers.GetAngularBoundsExpansion(motionState.Velocity.Angular.Length(), dt, maximumRadius, maximumAngularExpansion);
+ var speculativeMargin = motionState.Velocity.Linear.Length() * dt + angularBoundsExpansion;
+ speculativeMargin = MathF.Max(collidable.MinimumSpeculativeMargin, MathF.Min(collidable.MaximumSpeculativeMargin, speculativeMargin));
+ collidable.SpeculativeMargin = speculativeMargin;
+ var maximumAllowedExpansion = collidable.Continuity.AllowExpansionBeyondSpeculativeMargin ? float.MaxValue : speculativeMargin;
+ BoundingBoxHelpers.GetBoundsExpansion(motionState.Velocity.Linear, dt, angularBoundsExpansion, out var minExpansion, out var maxExpansion);
+ var broadcastMaximumBoundsExpansion = new Vector3(maximumAllowedExpansion);
+ minExpansion = Vector3.Max(-broadcastMaximumBoundsExpansion, minExpansion);
+ maxExpansion = Vector3.Min(broadcastMaximumBoundsExpansion, maxExpansion);
+
+ broadPhase.GetActiveBoundsPointers(collidable.BroadPhaseIndex, out var minPointer, out var maxPointer);
+ *minPointer = motionState.Pose.Position + (min + minExpansion);
+ *maxPointer = motionState.Pose.Position + (max + maxExpansion);
}
}
@@ -220,27 +273,30 @@ public unsafe void ExecuteCompoundBatch(CompoundShapeBatch shape
var maxValue = new Vector3(-float.MaxValue);
for (int i = 0; i < batch.Count; ++i)
{
- ref var instance = ref batch[i];
- var bodyIndex = instance.Continuation.BodyIndex;
- //We have to clear out the bounds used by compounds, since each contributing body will merge their contribution into the whole.
- broadPhase.GetActiveBoundsPointers(activeSet.Collidables[bodyIndex].BroadPhaseIndex, out var min, out var max);
+ var shapeIndex = batch.ShapeIndices[i];
+ var bodyIndex = batch.Continuations[i].BodyIndex;
+ ref var motionState = ref batch.MotionStates[i];
+ //We have to clear out the bounds and speculative margin used by compounds, since each contributing body will merge their contribution into the whole.
+ ref var collidable = ref activeSet.Collidables[bodyIndex];
+ collidable.SpeculativeMargin = 0;
+ broadPhase.GetActiveBoundsPointers(collidable.BroadPhaseIndex, out var min, out var max);
*min = minValue;
*max = maxValue;
- shapeBatch.shapes[instance.ShapeIndex].AddChildBoundsToBatcher(ref this, instance.Pose, instance.Velocities, bodyIndex);
+ shapeBatch.shapes[batch.ShapeIndices[i]].AddChildBoundsToBatcher(ref this, motionState.Pose, motionState.Velocity, bodyIndex);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- void Add(TypedIndex shapeIndex, in RigidPose pose, in BodyVelocity velocity, BoundsContinuation continuation)
+ void Add(TypedIndex shapeIndex, RigidPose pose, BodyVelocity velocity, BoundsContinuation continuation)
{
var typeIndex = shapeIndex.Type;
Debug.Assert(typeIndex >= 0 && typeIndex < batches.Length, "The preallocated type batch array should be able to hold every type index. Is the type index broken?");
ref var batchSlot = ref batches[typeIndex];
- if (!batchSlot.Span.Allocated)
+ if (!batchSlot.Allocated)
{
//No list exists for this type yet.
- batchSlot = new QuickList(CollidablesPerFlush, pool);
+ batchSlot = new BoundingBoxBatch(pool, CollidablesPerFlush);
if (typeIndex < minimumBatchIndex)
minimumBatchIndex = typeIndex;
if (typeIndex > maximumBatchIndex)
@@ -250,16 +306,10 @@ void Add(TypedIndex shapeIndex, in RigidPose pose, in BodyVelocity velocity, Bou
//It technically opens the door for vectorizing their child pose calculations, but those are almost certainly not worth vectorizing anyway.
//May want to consider directly triggering a bounds dispatch for compounds here.
//(Doing so WOULD be more complicated, though.)
- ref var instance = ref batchSlot.AllocateUnsafely();
- var shapeBatch = shapes[typeIndex];
- instance.Pose = pose;
- instance.Velocities = velocity;
- instance.ShapeIndex = shapeIndex.Index;
- instance.Continuation = continuation;
-
+ batchSlot.Add(shapeIndex.Index, pose, velocity, continuation);
if (batchSlot.Count == CollidablesPerFlush)
{
- shapeBatch.ComputeBounds(ref this);
+ shapes[typeIndex].ComputeBounds(ref this);
batchSlot.Count = 0;
}
}
@@ -314,10 +364,7 @@ public void Flush()
shapes[i].ComputeBounds(ref this);
}
//Dispose of the batch and any associated buffers; since the flush is one pass, we won't be needing this again.
- if (batch.Span.Allocated)
- {
- batch.Dispose(pool);
- }
+ batch.Dispose(pool);
}
pool.Return(ref batches);
}
diff --git a/BepuPhysics/Collidables/Box.cs b/BepuPhysics/Collidables/Box.cs
index ffbbca4f2..f59537c73 100644
--- a/BepuPhysics/Collidables/Box.cs
+++ b/BepuPhysics/Collidables/Box.cs
@@ -52,7 +52,7 @@ public Box(float width, float height, float length)
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly void ComputeBounds(in Quaternion orientation, out Vector3 min, out Vector3 max)
+ public readonly void ComputeBounds(Quaternion orientation, out Vector3 min, out Vector3 max)
{
Matrix3x3.CreateFromQuaternion(orientation, out var basis);
var x = HalfWidth * basis.X;
@@ -69,7 +69,7 @@ public readonly void ComputeAngularExpansionData(out float maximumRadius, out fl
maximumAngularExpansion = maximumRadius - Vector4.Min(new Vector4(HalfLength), Vector4.Min(new Vector4(HalfHeight), new Vector4(HalfLength))).X;
}
- public readonly bool RayTest(in RigidPose pose, in Vector3 origin, in Vector3 direction, out float t, out Vector3 normal)
+ public readonly bool RayTest(in RigidPose pose, Vector3 origin, Vector3 direction, out float t, out Vector3 normal)
{
var offset = origin - pose.Position;
Matrix3x3.CreateFromQuaternion(pose.Orientation, out var orientation);
@@ -146,8 +146,9 @@ public readonly bool RayTest(in RigidPose pose, in Vector3 origin, in Vector3 di
return true;
}
- public readonly void ComputeInertia(float mass, out BodyInertia inertia)
+ public readonly BodyInertia ComputeInertia(float mass)
{
+ BodyInertia inertia;
inertia.InverseMass = 1f / mass;
var x2 = HalfWidth * HalfWidth;
var y2 = HalfHeight * HalfHeight;
@@ -158,9 +159,10 @@ public readonly void ComputeInertia(float mass, out BodyInertia inertia)
inertia.InverseInertiaTensor.ZX = 0;
inertia.InverseInertiaTensor.ZY = 0;
inertia.InverseInertiaTensor.ZZ = inertia.InverseMass * 3 / (x2 + y2);
+ return inertia;
}
- public readonly ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapeBatches)
+ public static ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapeBatches)
{
return new ConvexShapeBatch(pool, initialCapacity);
}
@@ -169,7 +171,7 @@ public readonly ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity
/// Type id of box shapes.
///
public const int Id = 2;
- public readonly int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
+ public static int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
}
@@ -197,7 +199,7 @@ public void WriteFirst(in Box source)
public bool AllowOffsetMemoryAccess => true;
public int InternalAllocationSize => 0;
- public void Initialize(in RawBuffer memory) { }
+ public void Initialize(in Buffer memory) { }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSlot(int index, in Box source)
@@ -219,7 +221,7 @@ public void GetBounds(ref QuaternionWide orientations, int countInBundle, out Ve
maximumAngularExpansion = maximumRadius - Vector.Min(HalfLength, Vector.Min(HalfHeight, HalfLength));
}
- public int MinimumWideRayCount
+ public static int MinimumWideRayCount
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
@@ -228,7 +230,7 @@ public int MinimumWideRayCount
}
}
- public void RayTest(ref RigidPoses pose, ref RayWide ray, out Vector intersected, out Vector t, out Vector3Wide normal)
+ public void RayTest(ref RigidPoseWide pose, ref RayWide ray, out Vector intersected, out Vector t, out Vector3Wide normal)
{
Vector3Wide.Subtract(ray.Origin, pose.Position, out var offset);
Matrix3x3Wide.CreateFromQuaternion(pose.Orientation, out var orientation);
diff --git a/BepuPhysics/Collidables/Capsule.cs b/BepuPhysics/Collidables/Capsule.cs
index c6efb2d35..b5cb17f83 100644
--- a/BepuPhysics/Collidables/Capsule.cs
+++ b/BepuPhysics/Collidables/Capsule.cs
@@ -47,7 +47,7 @@ public readonly void ComputeAngularExpansionData(out float maximumRadius, out fl
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly void ComputeBounds(in Quaternion orientation, out Vector3 min, out Vector3 max)
+ public readonly void ComputeBounds(Quaternion orientation, out Vector3 min, out Vector3 max)
{
QuaternionEx.TransformUnitY(orientation, out var segmentOffset);
max = Vector3.Abs(HalfLength * segmentOffset) + new Vector3(Radius);
@@ -55,7 +55,7 @@ public readonly void ComputeBounds(in Quaternion orientation, out Vector3 min, o
}
- public readonly bool RayTest(in RigidPose pose, in Vector3 origin, in Vector3 direction, out float t, out Vector3 normal)
+ public readonly bool RayTest(in RigidPose pose, Vector3 origin, Vector3 direction, out float t, out Vector3 normal)
{
//It's convenient to work in local space, so pull the ray into the capsule's local space.
Matrix3x3.CreateFromQuaternion(pose.Orientation, out var orientation);
@@ -69,8 +69,7 @@ public readonly bool RayTest(in RigidPose pose, in Vector3 origin, in Vector3 di
//Move the origin up to the earliest possible impact time. This isn't necessary for math reasons, but it does help avoid some numerical problems.
var tOffset = -Vector3.Dot(o, d) - (HalfLength + Radius);
- if (tOffset < 0)
- tOffset = 0;
+ tOffset = float.Max(0, tOffset);
o += d * tOffset;
var oh = new Vector3(o.X, 0, o.Z);
var dh = new Vector3(d.X, 0, d.Z);
@@ -97,7 +96,7 @@ public readonly bool RayTest(in RigidPose pose, in Vector3 origin, in Vector3 di
normal = new Vector3();
return false;
}
- t = (-b - (float)Math.Sqrt(discriminant)) / a;
+ t = (-b - MathF.Sqrt(discriminant)) / a;
if (t < -tOffset)
t = -tOffset;
var cylinderHitLocation = o + d * t;
@@ -121,7 +120,11 @@ public readonly bool RayTest(in RigidPose pose, in Vector3 origin, in Vector3 di
else
{
//The ray is parallel to the axis; the impact is on a spherical cap or nothing.
- sphereY = d.Y > 0 ? -HalfLength : HalfLength;
+ //Note that the sphere cap is nudged forward to match the origin of the ray.
+ //This is just a simple way to capture the case where the ray starts inside the capsule, but too far to up/down to hit the cap chosen by d.Y.
+ sphereY = d.Y > 0 ?
+ float.Max(float.Min(HalfLength, o.Y), -HalfLength) :
+ float.Min(float.Max(-HalfLength, o.Y), HalfLength);
}
var os = o - new Vector3(0, sphereY, 0);
@@ -144,9 +147,8 @@ public readonly bool RayTest(in RigidPose pose, in Vector3 origin, in Vector3 di
normal = new Vector3();
return false;
}
- t = -capB - (float)Math.Sqrt(capDiscriminant);
- if (t < -tOffset)
- t = -tOffset;
+ t = -capB - MathF.Sqrt(capDiscriminant);
+ t = float.Max(t, -tOffset);
normal = (os + d * t) / Radius;
t = (t + tOffset) * inverseDLength;
Matrix3x3.Transform(normal, orientation, out normal);
@@ -154,8 +156,9 @@ public readonly bool RayTest(in RigidPose pose, in Vector3 origin, in Vector3 di
}
- public readonly void ComputeInertia(float mass, out BodyInertia inertia)
+ public readonly BodyInertia ComputeInertia(float mass)
{
+ BodyInertia inertia;
inertia.InverseMass = 1f / mass;
var r2 = Radius * Radius;
var h2 = HalfLength * HalfLength;
@@ -173,9 +176,10 @@ public readonly void ComputeInertia(float mass, out BodyInertia inertia)
inertia.InverseInertiaTensor.ZX = 0;
inertia.InverseInertiaTensor.ZY = 0;
inertia.InverseInertiaTensor.ZZ = inertia.InverseInertiaTensor.XX;
+ return inertia;
}
- public readonly ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapeBatches)
+ public static ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapeBatches)
{
return new ConvexShapeBatch(pool, initialCapacity);
}
@@ -186,7 +190,7 @@ public readonly ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity
/// Type id of capsule shapes.
///
public const int Id = 1;
- public readonly int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
+ public static int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
}
public struct CapsuleWide : IShapeWide
@@ -210,7 +214,7 @@ public void WriteFirst(in Capsule source)
public bool AllowOffsetMemoryAccess => true;
public int InternalAllocationSize => 0;
- public void Initialize(in RawBuffer memory) { }
+ public void Initialize(in Buffer memory) { }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSlot(int index, in Capsule source)
@@ -221,7 +225,7 @@ public void WriteSlot(int index, in Capsule source)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void GetBounds(ref QuaternionWide orientations, int countInBundle, out Vector maximumRadius, out Vector maximumAngularExpansion, out Vector3Wide min, out Vector3Wide max)
{
- QuaternionWide.TransformUnitY(orientations, out var segmentOffset);
+ var segmentOffset = QuaternionWide.TransformUnitY(orientations);
Vector3Wide.Scale(segmentOffset, HalfLength, out segmentOffset);
Vector3Wide.Abs(segmentOffset, out segmentOffset);
@@ -234,7 +238,7 @@ public void GetBounds(ref QuaternionWide orientations, int countInBundle, out Ve
maximumAngularExpansion = HalfLength;
}
- public int MinimumWideRayCount
+ public static int MinimumWideRayCount
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
@@ -243,7 +247,7 @@ public int MinimumWideRayCount
}
}
- public void RayTest(ref RigidPoses pose, ref RayWide ray, out Vector intersected, out Vector t, out Vector3Wide normal)
+ public void RayTest(ref RigidPoseWide pose, ref RayWide ray, out Vector intersected, out Vector t, out Vector3Wide normal)
{
//It's convenient to work in local space, so pull the ray into the capsule's local space.
Matrix3x3Wide.CreateFromQuaternion(pose.Orientation, out var orientation);
@@ -282,10 +286,14 @@ public void RayTest(ref RigidPoses pose, ref RayWide ray, out Vector inters
var useCylinder = Vector.BitwiseAnd(Vector.GreaterThanOrEqual(cylinderHitLocation.Y, -HalfLength), Vector.LessThanOrEqual(cylinderHitLocation.Y, HalfLength));
//Intersect the spherical cap for any lane which ended up not using the cylinder.
- Vector sphereY = Vector.ConditionalSelect(
- Vector.BitwiseOr(
- Vector.BitwiseAnd(Vector.GreaterThan(cylinderHitLocation.Y, HalfLength), rayIsntParallel),
- Vector.AndNot(Vector.LessThanOrEqual(d.Y, Vector.Zero), rayIsntParallel)), HalfLength, -HalfLength);
+ //Note that the sphere cap is nudged forward in the parallel case to match the origin of the ray.
+ //This is just a simple way to capture the case where the ray starts inside the capsule, but too far to up/down to hit the cap chosen by d.Y.
+ var negatedHalfLength = -HalfLength;
+ var parallelSphereY = Vector.ConditionalSelect(Vector.LessThan(d.Y, Vector.Zero),
+ Vector.Max(negatedHalfLength, Vector.Min(o.Y, HalfLength)),
+ Vector.Min(HalfLength, Vector.Max(o.Y, negatedHalfLength)));
+ var nonParallelSphereY = Vector.ConditionalSelect(Vector.GreaterThan(cylinderHitLocation.Y, HalfLength), HalfLength, negatedHalfLength);
+ Vector sphereY = Vector.ConditionalSelect(rayIsntParallel, nonParallelSphereY, parallelSphereY);
o.Y -= sphereY;
Vector3Wide.Dot(o, d, out var capB);
diff --git a/BepuPhysics/Collidables/Collidable.cs b/BepuPhysics/Collidables/Collidable.cs
index 0eefc8528..1d34a7f8c 100644
--- a/BepuPhysics/Collidables/Collidable.cs
+++ b/BepuPhysics/Collidables/Collidable.cs
@@ -1,20 +1,23 @@
-using System.Runtime.CompilerServices;
+using BepuPhysics.CollisionDetection;
using System.Runtime.InteropServices;
namespace BepuPhysics.Collidables
{
+ ///
+ /// Defines how a collidable will handle collision detection in the presence of velocity.
+ ///
public enum ContinuousDetectionMode
{
///
- /// No dedicated continuous detection is performed. Default speculative contact generation will occur within the speculative margin.
+ /// No sweep tests are performed. Default speculative contact generation will occur within the speculative margin.
/// The collidable's bounding box will not be expanded by velocity beyond the speculative margin.
- /// This is the cheapest mode, but it may miss collisions. Note that if a Discrete mode collidable is moving quickly, the fact that its bounding box is not expanded
- /// may cause it to miss a collision even with a non-Discrete collidable.
+ /// This is the cheapest mode. If a Discrete mode collidable is moving quickly and the maximum speculative margin is limited,
+ /// the fact that its bounding box is not expanded may cause it to miss a collision even with a non-Discrete collidable.
///
Discrete = 0,
///
- /// No dedicated continuous detection is performed. Default speculative contact generation will occur within the speculative margin.
- /// The collidable's bounding box will be expanded by velocity beyond the speculative margin if necessary.
+ /// No sweep tests are performed. Default speculative contact generation will occur within the speculative margin.
+ /// The collidable's bounding box will be expanded by velocity without being limited by the speculative margin.
/// This is useful when a collidable may move quickly and does not itself require continuous detection, but there exist other collidables with continuous modes
/// that should avoid missing collisions.
///
@@ -26,50 +29,60 @@ public enum ContinuousDetectionMode
Continuous = 2,
}
- [StructLayout(LayoutKind.Explicit)]
- public struct ContinuousDetectionSettings
+ ///
+ /// Defines how a collidable handles collisions with significant velocity.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ContinuousDetection
{
///
/// The continuous collision detection mode.
///
- [FieldOffset(0)]
public ContinuousDetectionMode Mode;
+
///
- /// If using ContinuousDetectionMode.Continuous, MinimumSweepTimestep is the minimum progress that the sweep test will make when searching for the first time of impact.
- /// Collisions lasting less than MinimumProgress may be missed by the sweep test. Using larger values can significantly increase the performance of sweep tests.
+ /// If using , this defines the minimum progress that the sweep test will make when searching for the first time of impact.
+ /// Collisions lasting less than may be missed by the sweep test. Using larger values can significantly increase the performance of sweep tests.
///
- [FieldOffset(4)]
public float MinimumSweepTimestep;
+
///
- /// If using ContinuousDetectionMode.Continuous, sweep tests will terminate if the time of impact region has been refined to be smaller than SweepConvergenceThreshold.
+ /// If using , sweep tests will terminate if the time of impact region has been refined to be smaller than .
/// Values closer to zero will converge more closely to the true time of impact, but for speculative contact generation larger values usually work fine.
/// Larger values allow the sweep to terminate much earlier and can significantly improve sweep performance.
///
- [FieldOffset(8)]
public float SweepConvergenceThreshold;
- public bool AllowExpansionBeyondSpeculativeMargin { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return (uint)Mode > 0; } }
+ ///
+ /// Gets whether the continuous collision detection configuration will permit bounding box expansion beyond the calculated speculative margin.
+ ///
+ public bool AllowExpansionBeyondSpeculativeMargin => (uint)Mode > 0;
///
- /// No dedicated continuous detection will be performed. Default speculative contact generation will occur within the speculative margin.
+ /// No sweep tests are performed. Default speculative contact generation will occur within the speculative margin.
/// The collidable's bounding box will not be expanded by velocity beyond the speculative margin.
- /// This is the cheapest mode, but it may miss collisions. Note that if a Discrete mode collidable is moving quickly, the fact that its bounding box is not expanded
- /// may cause it to miss a collision even with a non-Discrete collidable.
+ /// This can be marginally cheaper than Passive modes if using a limited maximum speculative margin. If a Discrete mode collidable is moving quickly and the maximum speculative margin is limited,
+ /// the fact that its bounding box is not expanded may cause it to miss a collision even with a non-Discrete collidable.
+ /// Note that Discrete and Passive only differ if maximum speculative margin is restricted.
///
- public static ContinuousDetectionSettings Discrete
+ /// Detection settings for the given discrete configuration.
+ public static ContinuousDetection Discrete
{
- get { return new ContinuousDetectionSettings(); }
+ get
+ {
+ return new() { Mode = ContinuousDetectionMode.Discrete };
+ }
}
///
- /// No dedicated continuous detection is performed. Default speculative contact generation will occur within the speculative margin.
- /// The collidable's bounding box will be expanded by velocity beyond the speculative margin if necessary.
- /// This is useful when a collidable may move quickly and does not itself require continuous detection, but there exist other collidables with continuous modes
- /// that should avoid missing collisions.
+ /// No sweep tests are performed. Default speculative contact generation will occur within the speculative margin.
+ /// The collidable's bounding box and speculative margin will be expanded by velocity.
+ /// This is useful when a collidable may move quickly and does not itself require continuous detection, but there exist other collidables with continuous modes that should avoid missing collisions.
///
- public static ContinuousDetectionSettings Passive
+ /// Detection settings for the passive configuration.
+ public static ContinuousDetection Passive
{
- get { return new ContinuousDetectionSettings() { Mode = ContinuousDetectionMode.Passive }; }
+ get { return new() { Mode = ContinuousDetectionMode.Passive }; }
}
///
@@ -82,35 +95,59 @@ public static ContinuousDetectionSettings Passive
/// If the region has been refined to be smaller than SweepConvergenceThreshold, the sweep will terminate.
/// Values closer to zero will converge more closely to the true time of impact, but for speculative contact generation larger values usually work fine.
/// Larger values allow the sweep to terminate much earlier and can significantly improve sweep performance.
- /// Settings reflecting a continuous detection mode.
- public static ContinuousDetectionSettings Continuous(float minimumSweepTimestep, float sweepConvergenceThreshold)
+ /// Detection settings for the given continuous configuration.
+ public static ContinuousDetection Continuous(float minimumSweepTimestep = 1e-3f, float sweepConvergenceThreshold = 1e-3f)
{
- return new ContinuousDetectionSettings { Mode = ContinuousDetectionMode.Continuous, MinimumSweepTimestep = minimumSweepTimestep, SweepConvergenceThreshold = sweepConvergenceThreshold };
+ return new()
+ {
+ Mode = ContinuousDetectionMode.Continuous,
+ MinimumSweepTimestep = minimumSweepTimestep,
+ SweepConvergenceThreshold = sweepConvergenceThreshold
+ };
}
}
///
- /// Description of a collidable instance living in the broad phase and able to generate collision pairs.
- /// Collidables with a ShapeIndex that points to nothing (a default constructed TypedIndex) do not actually refer to any existing Collidable.
+ /// Description of a collidable used by a body living in the broad phase and able to generate collision pairs.
+ /// Collidables with a ShapeIndex that points to nothing (a default constructed ) are not capable of colliding with anything.
/// This can be used for a body which needs no collidable representation.
///
public struct Collidable
{
+ ///
+ /// Index of the shape used by the body. While this can be changed, any transition from shapeless->shapeful or shapeful->shapeless must be reported to the broad phase.
+ /// If you need to perform such a transition, consider using or ; those functions update the relevant state.
+ ///
+ public TypedIndex Shape;
///
/// Continuous collision detection settings for this collidable. Includes the collision detection mode to use and tuning variables associated with those modes.
///
- public ContinuousDetectionSettings Continuity;
-
+ public ContinuousDetection Continuity;
///
- /// Index of the shape used by the body. While this can be changed, any transition from shapeless->shapeful or shapeful->shapeless must be reported to the broad phase.
- /// If you need to perform such a transition, consider using Bodies.ChangeShape or Bodies.ApplyDescription; those functions update the relevant state.
+ /// Lower bound on the value of the speculative margin used by the collidable.
///
- public TypedIndex Shape;
+ /// 0 tends to be a good default value. Higher values can be chosen if velocity magnitude is a poor proxy for speculative margins, but these cases are rare.
+ /// In those cases, try to use the smallest value that still satisfies requirements to avoid creating unnecessary contact constraints.
+ public float MinimumSpeculativeMargin;
+ ///
+ /// Upper bound on the value of the speculative margin used by the collidable.
+ ///
+ /// tends to be a good default value for discrete or passive mode collidables.
+ /// The speculative margin will increase in size proportional to velocity magnitude, so having an unlimited maximum won't cost extra if the body isn't moving fast.
+ /// Smaller values can be useful for improving performance in chaotic situations where missing a collision is acceptable. When using , a speculative margin larger than the velocity magnitude will result in the sweep test being skipped, so lowering the maximum margin can help avoid ghost collisions.
+ ///
+ public float MaximumSpeculativeMargin;
+
///
- /// Size of the margin around the surface of the shape in which contacts can be generated. These contacts will have negative depth and only contribute if the frame's velocities
- /// would push the shapes of a pair into overlap. This should be positive to avoid jittering. It can also be used as a form of continuous collision detection, but excessively
- /// high values combined with fast motion may result in visible 'ghost collision' artifacts.
- /// For continuous collision detection with less chance of ghost collisions, use the dedicated continuous collision detection modes.
+ /// Automatically computed size of the margin around the surface of the shape in which contacts can be generated. These contacts will have negative depth and only contribute if the frame's velocities
+ /// would push the shapes of a pair into overlap.
+ /// This is automatically set by bounding box prediction each frame, and is bound by the collidable's and values.
+ /// The effective speculative margin for a collision pair can also be modified from callbacks.
+ /// This should be positive to avoid jittering.
+ /// It can also be used as a form of continuous collision detection, but excessively high values combined with fast motion may result in visible 'ghost collision' artifacts.
+ /// For continuous collision detection with less chance of ghost collisions, use .
+ /// If using , consider setting to a smaller value to help filter ghost collisions.
+ /// For more information, see the ContinuousCollisionDetection.md documentation.
///
public float SpeculativeMargin;
///
diff --git a/BepuPhysics/Collidables/CollidableDescription.cs b/BepuPhysics/Collidables/CollidableDescription.cs
index 06c0b8953..6302a760d 100644
--- a/BepuPhysics/Collidables/CollidableDescription.cs
+++ b/BepuPhysics/Collidables/CollidableDescription.cs
@@ -1,31 +1,117 @@
-namespace BepuPhysics.Collidables
+using System.Runtime.InteropServices;
+
+namespace BepuPhysics.Collidables
{
+ ///
+ /// Describes a collidable and how it should handle continuous collision detection.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
public struct CollidableDescription
{
+ ///
+ /// Shape of the collidable.
+ ///
public TypedIndex Shape;
- public float SpeculativeMargin;
- public ContinuousDetectionSettings Continuity;
+ ///
+ /// Continuous collision detection settings used by the collidable.
+ ///
+ public ContinuousDetection Continuity;
+ ///
+ /// Lower bound on the value of the speculative margin used by the collidable.
+ ///
+ /// 0 tends to be a good default value. Higher values can be chosen if velocity magnitude is a poor proxy for speculative margins, but these cases are rare.
+ /// In those cases, try to use the smallest value that still satisfies requirements to avoid creating unnecessary contact constraints.
+ public float MinimumSpeculativeMargin;
+ ///
+ /// Upper bound on the value of the speculative margin used by the collidable.
+ ///
+ /// tends to be a good default value for discrete or passive mode collidables.
+ /// The speculative margin will increase in size proportional to velocity magnitude, so having an unlimited maximum won't cost extra if the body isn't moving fast.
+ /// Smaller values can be useful for improving performance in chaotic situations where missing a collision is acceptable. When using , a speculative margin larger than the velocity magnitude will result in the sweep test being skipped, so lowering the maximum margin can help avoid ghost collisions.
+ ///
+ public float MaximumSpeculativeMargin;
///
/// Constructs a new collidable description.
///
/// Shape used by the collidable.
- /// Radius of the margin in which to allow speculative contact generation.
+ /// Lower bound on the value of the speculative margin used by the collidable.
+ /// Upper bound on the value of the speculative margin used by the collidable.
/// Continuous collision detection settings for the collidable.
- public CollidableDescription(TypedIndex shape, float speculativeMargin, in ContinuousDetectionSettings continuity)
+ public CollidableDescription(TypedIndex shape, float minimumSpeculativeMargin, float maximumSpeculativeMargin, ContinuousDetection continuity)
{
Shape = shape;
- SpeculativeMargin = speculativeMargin;
+ MinimumSpeculativeMargin = minimumSpeculativeMargin;
+ MaximumSpeculativeMargin = maximumSpeculativeMargin;
Continuity = continuity;
}
///
- /// Constructs a new collidable description with default discrete continuity.
+ /// Constructs a new collidable description with .
///
/// Shape used by the collidable.
- /// Radius of the margin in which to allow speculative contact generation.
- public CollidableDescription(TypedIndex shape, float speculativeMargin) : this(shape, speculativeMargin, default)
+ /// Lower bound on the value of the speculative margin used by the collidable.
+ /// Upper bound on the value of the speculative margin used by the collidable.
+ public CollidableDescription(TypedIndex shape, float minimumSpeculativeMargin, float maximumSpeculativeMargin)
+ {
+ Shape = shape;
+ MinimumSpeculativeMargin = minimumSpeculativeMargin;
+ MaximumSpeculativeMargin = maximumSpeculativeMargin;
+ Continuity = ContinuousDetection.Discrete;
+ }
+
+ ///
+ /// Constructs a new collidable description. Uses 0 for the .
+ ///
+ /// Shape used by the collidable.
+ /// Upper bound on the value of the speculative margin used by the collidable.
+ /// Continuous collision detection settings for the collidable.
+ public CollidableDescription(TypedIndex shape, float maximumSpeculativeMargin, ContinuousDetection continuity)
+ {
+ Shape = shape;
+ MinimumSpeculativeMargin = 0;
+ MaximumSpeculativeMargin = maximumSpeculativeMargin;
+ Continuity = continuity;
+ }
+
+ ///
+ /// Constructs a new collidable description. Uses 0 for the and for the .
+ ///
+ /// Shape used by the collidable.
+ /// Continuous collision detection settings for the collidable.
+ public CollidableDescription(TypedIndex shape, ContinuousDetection continuity)
+ {
+ Shape = shape;
+ MinimumSpeculativeMargin = 0;
+ MaximumSpeculativeMargin = float.MaxValue;
+ Continuity = continuity;
+ }
+
+ ///
+ /// Constructs a new collidable description with . Will use a of 0 and a of .
+ ///
+ /// Shape used by the collidable.
+ /// and are equivalent in behavior when the is since they both result in the same (unbounded) expansion of body bounding boxes in response to velocity.
+ public CollidableDescription(TypedIndex shape) : this(shape, 0, float.MaxValue, ContinuousDetection.Passive)
+ {
+ }
+
+ ///
+ /// Constructs a new collidable description with . Will use a minimum speculative margin of 0 and the given maximumSpeculativeMargin.
+ ///
+ /// Shape used by the collidable.
+ /// Maximum speculative margin to be used with the discrete continuity configuration.
+ public CollidableDescription(TypedIndex shape, float maximumSpeculativeMargin) : this(shape, 0, maximumSpeculativeMargin, ContinuousDetection.Discrete)
+ {
+ }
+
+ ///
+ /// Constructs a new collidable description with . Will use a minimum speculative margin of 0 and a maximum of .
+ ///
+ /// Shape index to use for the collidable.
+ public static implicit operator CollidableDescription(TypedIndex shapeIndex)
{
+ return new CollidableDescription(shapeIndex);
}
}
}
diff --git a/BepuPhysics/Collidables/CollidableReference.cs b/BepuPhysics/Collidables/CollidableReference.cs
index d9168f345..c9c329eab 100644
--- a/BepuPhysics/Collidables/CollidableReference.cs
+++ b/BepuPhysics/Collidables/CollidableReference.cs
@@ -2,9 +2,13 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace BepuPhysics.Collidables
{
+ ///
+ /// Represents how a collidable can interact and move.
+ ///
public enum CollidableMobility
{
///
@@ -21,8 +25,15 @@ public enum CollidableMobility
Static = 2
}
+ ///
+ /// Uses a bitpacked representation to refer to a body or static collidable.
+ ///
+ [StructLayout(LayoutKind.Sequential, Size = 4)]
public struct CollidableReference : IEquatable
{
+ ///
+ /// Bitpacked representation of the collidable reference.
+ ///
public uint Packed;
///
@@ -102,7 +113,6 @@ public CollidableReference(CollidableMobility mobility, BodyHandle handle)
///
/// Creates a collidable reference for a static.
///
- /// Mobility type of the owner of the collidable.
/// Handle of the owner of the collidable.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CollidableReference(StaticHandle handle)
diff --git a/BepuPhysics/Collidables/Compound.cs b/BepuPhysics/Collidables/Compound.cs
index 3624a4b7e..dc42ec0ca 100644
--- a/BepuPhysics/Collidables/Compound.cs
+++ b/BepuPhysics/Collidables/Compound.cs
@@ -6,290 +6,404 @@
using BepuUtilities;
using BepuPhysics.Trees;
using BepuPhysics.CollisionDetection.CollisionTasks;
+using System.Runtime.InteropServices;
+using System.Diagnostics.CodeAnalysis;
-namespace BepuPhysics.Collidables
+namespace BepuPhysics.Collidables;
+
+///
+/// Shape and pose of a child within a compound shape.
+///
+[StructLayout(LayoutKind.Sequential)]
+public struct CompoundChild
{
- public struct CompoundChild
+ ///
+ /// Local orientation of the child in the compound.
+ ///
+ public Quaternion LocalOrientation;
+ ///
+ /// Local position of the child in the compound.
+ ///
+ public Vector3 LocalPosition;
+ ///
+ /// Index of the shape within whatever shape collection holds the compound's child shape data.
+ ///
+ public TypedIndex ShapeIndex;
+
+ ///
+ /// Creates a compound child.
+ ///
+ /// Pose of the compound child in the local space of the parent shape.
+ /// Index of the shape used by the child.
+ public CompoundChild(in RigidPose pose, TypedIndex shapeIndex)
{
- public TypedIndex ShapeIndex;
- public RigidPose LocalPose;
+ LocalOrientation = pose.Orientation;
+ LocalPosition = pose.Position;
+ ShapeIndex = shapeIndex;
}
- struct CompoundChildShapeTester : IShapeRayHitHandler
+ ///
+ /// Returns a reference to the memory of the as a .
+ ///
+ /// Reference to this compound child as a pose.
+ [UnscopedRef]
+ public ref RigidPose AsPose()
{
- //We use a non-generic hit handler to capture the final result of a leaf test.
- //This requires caching out the T and Normal for reading by whatever ended up calling this, but it's worth it to avoid AOT pipelines barfing on infinite recursion.
- public float T;
- public Vector3 Normal;
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool AllowTest(int childIndex)
- {
- Debug.Assert(childIndex == 0, "Compounds can contain only convexes, so the child index is always zero.");
- //The actual test filtering took place in the TestLeaf function, where we call Handler.AllowTest.
- return true;
- }
+ return ref Unsafe.As(ref this);
+ }
+}
- public void OnRayHit(in RayData ray, ref float maximumT, float t, in Vector3 normal, int childIndex)
- {
- Debug.Assert(childIndex == 0, "Compounds can contain only convexes, so the child index is always zero.");
- T = t;
- Normal = normal;
- }
+struct CompoundChildShapeTester : IShapeRayHitHandler
+{
+ //We use a non-generic hit handler to capture the final result of a leaf test.
+ //This requires caching out the T and Normal for reading by whatever ended up calling this, but it's worth it to avoid AOT pipelines barfing on infinite recursion.
+ public float T;
+ public Vector3 Normal;
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool AllowTest(int childIndex)
+ {
+ Debug.Assert(childIndex == 0, "Compounds can contain only convexes, so the child index is always zero.");
+ //The actual test filtering took place in the TestLeaf function, where we call Handler.AllowTest.
+ return true;
+ }
+
+ public void OnRayHit(in RayData ray, ref float maximumT, float t, Vector3 normal, int childIndex)
+ {
+ Debug.Assert(childIndex == 0, "Compounds can contain only convexes, so the child index is always zero.");
+ T = t;
+ Normal = normal;
}
+}
+
+///
+/// Minimalist compound shape containing a list of child shapes. Does not make use of any internal acceleration structure; should be used only with small groups of shapes.
+///
+public struct Compound : ICompoundShape
+{
+ ///
+ /// Buffer of children within this compound.
+ ///
+ public Buffer Children;
///
- /// Minimalist compound shape containing a list of child shapes. Does not make use of any internal acceleration structure; should be used only with small groups of shapes.
+ /// Creates a compound shape with no acceleration structure.
///
- public struct Compound : ICompoundShape
+ /// Set of children in the compound.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Compound(Buffer children)
{
- ///
- /// Buffer of children within this compound.
- ///
- public Buffer Children;
+ Debug.Assert(children.Length > 0, "Compounds must have a nonzero number of children.");
+ Children = children;
+ }
- ///
- /// Creates a compound shape with no acceleration structure.
- ///
- /// Set of children in the compound.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public Compound(Buffer children)
+ ///
+ /// Checks if a shape index.
+ ///
+ /// Shape index to analyze.
+ /// Shape collection into which the index indexes.
+ /// True if the index is valid, false otherwise.
+ public static bool ValidateChildIndex(TypedIndex shapeIndex, Shapes shapeBatches)
+ {
+ if (shapeIndex.Type < 0 || shapeIndex.Type >= shapeBatches.RegisteredTypeSpan)
{
- Debug.Assert(children.Length > 0, "Compounds must have a nonzero number of children.");
- Children = children;
+ Debug.Fail("Child shape type needs to fit within the shape batch registered types.");
+ return false;
}
-
- ///
- /// Checks if a shape index.
- ///
- /// Shape index to analyze.
- /// Shape collection into which the index indexes.
- /// True if the index is valid, false otherwise.
- public static bool ValidateChildIndex(TypedIndex shapeIndex, Shapes shapeBatches)
+ var batch = shapeBatches[shapeIndex.Type];
+ if (shapeIndex.Index < 0 || shapeIndex.Index >= batch.Capacity)
{
- if (shapeIndex.Type < 0 || shapeIndex.Type >= shapeBatches.RegisteredTypeSpan)
- {
- Debug.Fail("Child shape type needs to fit within the shape batch registered types.");
- return false;
- }
- var batch = shapeBatches[shapeIndex.Type];
- if (shapeIndex.Index < 0 || shapeIndex.Index >= batch.Capacity)
- {
- Debug.Fail("Child shape index should point to a valid buffer location in the sahpe batch.");
- return false;
- }
- if (shapeBatches[shapeIndex.Type].Compound)
- {
- Debug.Fail("Child shape type should be convex.");
- return false;
- }
- //TODO: We don't have a cheap way to verify that a specific index actually contains a shape right now.
- return true;
+ Debug.Fail("Child shape index should point to a valid buffer location in the sahpe batch.");
+ return false;
}
-
- ///
- /// Checks if a set of children shape indices are all valid.
- ///
- /// Children to examine.
- /// Shape collection into which the children index.
- /// True if all child indices are valid, false otherwise.
- public static bool ValidateChildIndices(ref Buffer children, Shapes shapeBatches)
+ if (shapeBatches[shapeIndex.Type].Compound)
{
- for (int i = 0; i < children.Length; ++i)
- {
- ValidateChildIndex(children[i].ShapeIndex, shapeBatches);
- }
- return true;
+ Debug.Fail("Child shape type should be convex.");
+ return false;
}
+ //TODO: We don't have a cheap way to verify that a specific index actually contains a shape right now.
+ return true;
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void GetRotatedChildPose(in RigidPose localPose, in Quaternion orientation, out RigidPose rotatedChildPose)
- {
- QuaternionEx.ConcatenateWithoutOverlap(localPose.Orientation, orientation, out rotatedChildPose.Orientation);
- QuaternionEx.Transform(localPose.Position, orientation, out rotatedChildPose.Position);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void GetRotatedChildPose(in RigidPoses localPose, in QuaternionWide orientation, out Vector3Wide childPosition, out QuaternionWide childOrientation)
- {
- QuaternionWide.ConcatenateWithoutOverlap(localPose.Orientation, orientation, out childOrientation);
- QuaternionWide.TransformWithoutOverlap(localPose.Position, orientation, out childPosition);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void GetRotatedChildPose(in RigidPoses localPose, in QuaternionWide orientation, out RigidPoses rotatedChildPose)
- {
- GetRotatedChildPose(localPose, orientation, out rotatedChildPose.Position, out rotatedChildPose.Orientation);
- }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void GetWorldPose(in RigidPose localPose, in RigidPose transform, out RigidPose worldPose)
+ ///
+ /// Checks if a set of children shape indices are all valid.
+ ///
+ /// Children to examine.
+ /// Shape collection into which the children index.
+ /// True if all child indices are valid, false otherwise.
+ public static bool ValidateChildIndices(Span children, Shapes shapeBatches)
+ {
+ for (int i = 0; i < children.Length; ++i)
{
- GetRotatedChildPose(localPose, transform.Orientation, out worldPose);
- //TODO: This is an area that has to be updated for high precision poses. May be able to centralize positional work
- //by deferring it until the final bounds scatter step. Would require looking up the position then, but could be worth simplicity.
- worldPose.Position += transform.Position;
+ ValidateChildIndex(children[i].ShapeIndex, shapeBatches);
}
+ return true;
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void ComputeChildBounds(in CompoundChild child, in Quaternion orientation, Shapes shapeBatches, out Vector3 childMin, out Vector3 childMax)
- {
- GetRotatedChildPose(child.LocalPose, orientation, out var childPose);
- Debug.Assert(!shapeBatches[child.ShapeIndex.Type].Compound, "All children of a compound must be convex.");
- shapeBatches[child.ShapeIndex.Type].ComputeBounds(child.ShapeIndex.Index, childPose, out childMin, out childMax);
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetRotatedChildPose(in RigidPose localPose, Quaternion orientation, out RigidPose rotatedChildPose)
+ {
+ GetRotatedChildPose(localPose.Position, localPose.Orientation, orientation, out rotatedChildPose.Position, out rotatedChildPose.Orientation);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetRotatedChildPose(Vector3 localPosition, Quaternion localOrientation, Quaternion orientation, out RigidPose rotatedChildPose)
+ {
+ GetRotatedChildPose(localPosition, localOrientation, orientation, out rotatedChildPose.Position, out rotatedChildPose.Orientation);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetRotatedChildPose(Vector3 localPosition, Quaternion localOrientation, Quaternion parentOrientation, out Vector3 rotatedPosition, out Quaternion rotatedOrientation)
+ {
+ QuaternionEx.ConcatenateWithoutOverlap(localOrientation, parentOrientation, out rotatedOrientation);
+ QuaternionEx.Transform(localPosition, parentOrientation, out rotatedPosition);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetRotatedChildPose(in RigidPoseWide localPose, in QuaternionWide orientation, out Vector3Wide childPosition, out QuaternionWide childOrientation)
+ {
+ QuaternionWide.ConcatenateWithoutOverlap(localPose.Orientation, orientation, out childOrientation);
+ QuaternionWide.TransformWithoutOverlap(localPose.Position, orientation, out childPosition);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetRotatedChildPose(in RigidPoseWide localPose, in QuaternionWide orientation, out RigidPoseWide rotatedChildPose)
+ {
+ GetRotatedChildPose(localPose, orientation, out rotatedChildPose.Position, out rotatedChildPose.Orientation);
+ }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetWorldPose(in RigidPose localPose, in RigidPose transform, out RigidPose worldPose)
+ {
+ GetRotatedChildPose(localPose, transform.Orientation, out worldPose);
+ //TODO: This is an area that has to be updated for high precision poses. May be able to centralize positional work
+ //by deferring it until the final bounds scatter step. Would require looking up the position then, but could be worth simplicity.
+ worldPose.Position += transform.Position;
+ }
- public void ComputeBounds(in Quaternion orientation, Shapes shapeBatches, out Vector3 min, out Vector3 max)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void ComputeChildBounds(in CompoundChild child, Quaternion orientation, Shapes shapeBatches, out Vector3 childMin, out Vector3 childMax)
+ {
+ GetRotatedChildPose(child.LocalPosition, child.LocalOrientation, orientation, out var childPose);
+ Debug.Assert(!shapeBatches[child.ShapeIndex.Type].Compound, "All children of a compound must be convex.");
+ shapeBatches[child.ShapeIndex.Type].ComputeBounds(child.ShapeIndex.Index, childPose, out childMin, out childMax);
+ }
+
+ public void ComputeBounds(Quaternion orientation, Shapes shapeBatches, out Vector3 min, out Vector3 max)
+ {
+ ComputeChildBounds(Children[0], orientation, shapeBatches, out min, out max);
+ for (int i = 1; i < Children.Length; ++i)
{
- ComputeChildBounds(Children[0], orientation, shapeBatches, out min, out max);
- for (int i = 1; i < Children.Length; ++i)
- {
- ref var child = ref Children[i];
- ComputeChildBounds(Children[i], orientation, shapeBatches, out var childMin, out var childMax);
- BoundingBox.CreateMerged(min, max, childMin, childMax, out min, out max);
- }
+ ref var child = ref Children[i];
+ ComputeChildBounds(Children[i], orientation, shapeBatches, out var childMin, out var childMax);
+ BoundingBox.CreateMerged(min, max, childMin, childMax, out min, out max);
}
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void AddChildBoundsToBatcher(ref Buffer children, ref BoundingBoxBatcher batcher, in RigidPose pose, in BodyVelocity velocity, int bodyIndex)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void AddChildBoundsToBatcher(Buffer children, ref BoundingBoxBatcher batcher, in RigidPose pose, in BodyVelocity velocity, int bodyIndex)
+ {
+ //Note that this approximates the velocity of the child using a piecewise extrapolation using the parent's angular velocity.
+ //For significant angular velocities, this is actually wrong, but this is how v1 worked forever and it's cheap.
+ //May want to revisit this later- it would likely require that the BoundingBoxBatcher have a continuation, or to include more information
+ //for the convex path to condition on.
+ BodyVelocity childVelocity;
+ childVelocity.Angular = velocity.Angular;
+ for (int i = 0; i < children.Length; ++i)
{
- //Note that this approximates the velocity of the child using a piecewise extrapolation using the parent's angular velocity.
- //For significant angular velocities, this is actually wrong, but this is how v1 worked forever and it's cheap.
- //May want to revisit this later- it would likely require that the BoundingBoxBatcher have a continuation, or to include more information
- //for the convex path to condition on.
- BodyVelocity childVelocity;
- childVelocity.Angular = velocity.Angular;
- for (int i = 0; i < children.Length; ++i)
+ ref var child = ref children[i];
+ GetRotatedChildPose(child.LocalPosition, child.LocalOrientation, pose.Orientation, out var childPose);
+ var angularContributionToChildLinear = Vector3.Cross(velocity.Angular, childPose.Position);
+ var contributionLengthSquared = angularContributionToChildLinear.LengthSquared();
+ var localPoseRadiusSquared = childPose.Position.LengthSquared();
+ if (contributionLengthSquared > localPoseRadiusSquared)
{
- ref var child = ref children[i];
- GetRotatedChildPose(child.LocalPose, pose.Orientation, out var childPose);
- var angularContributionToChildLinear = Vector3.Cross(velocity.Angular, childPose.Position);
- var contributionLengthSquared = angularContributionToChildLinear.LengthSquared();
- var localPoseRadiusSquared = childPose.Position.LengthSquared();
- if (contributionLengthSquared > localPoseRadiusSquared)
- {
- angularContributionToChildLinear *= (float)(Math.Sqrt(localPoseRadiusSquared) / Math.Sqrt(contributionLengthSquared));
- }
- childVelocity.Linear = velocity.Linear + angularContributionToChildLinear;
- childPose.Position += pose.Position;
- batcher.AddCompoundChild(bodyIndex, children[i].ShapeIndex, childPose, childVelocity);
+ angularContributionToChildLinear *= (float)(Math.Sqrt(localPoseRadiusSquared) / Math.Sqrt(contributionLengthSquared));
}
+ childVelocity.Linear = velocity.Linear + angularContributionToChildLinear;
+ childPose.Position += pose.Position;
+ batcher.AddCompoundChild(bodyIndex, children[i].ShapeIndex, childPose, childVelocity);
}
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AddChildBoundsToBatcher(ref BoundingBoxBatcher batcher, in RigidPose pose, in BodyVelocity velocity, int bodyIndex)
- {
- AddChildBoundsToBatcher(ref Children, ref batcher, pose, velocity, bodyIndex);
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddChildBoundsToBatcher(ref BoundingBoxBatcher batcher, in RigidPose pose, in BodyVelocity velocity, int bodyIndex)
+ {
+ AddChildBoundsToBatcher(Children, ref batcher, pose, velocity, bodyIndex);
+ }
- public void RayTest(in RigidPose pose, in RayData ray, ref float maximumT, Shapes shapeBatches, ref TRayHitHandler hitHandler) where TRayHitHandler : struct, IShapeRayHitHandler
- {
- Matrix3x3.CreateFromQuaternion(pose.Orientation, out var orientation);
- RayData localRay;
- Matrix3x3.TransformTranspose(ray.Origin - pose.Position, orientation, out localRay.Origin);
- Matrix3x3.TransformTranspose(ray.Direction, orientation, out localRay.Direction);
- localRay.Id = 0;
+ public void RayTest(in RigidPose pose, in RayData ray, ref float maximumT, Shapes shapeBatches, BufferPool pool, ref TRayHitHandler hitHandler) where TRayHitHandler : struct, IShapeRayHitHandler
+ {
+ Matrix3x3.CreateFromQuaternion(pose.Orientation, out var orientation);
+ RayData localRay;
+ Matrix3x3.TransformTranspose(ray.Origin - pose.Position, orientation, out localRay.Origin);
+ Matrix3x3.TransformTranspose(ray.Direction, orientation, out localRay.Direction);
+ localRay.Id = 0;
- for (int i = 0; i < Children.Length; ++i)
+ for (int i = 0; i < Children.Length; ++i)
+ {
+ if (hitHandler.AllowTest(i))
{
- if (hitHandler.AllowTest(i))
+ ref var child = ref Children[i];
+ CompoundChildShapeTester tester;
+ tester.T = -1;
+ tester.Normal = default;
+ shapeBatches[child.ShapeIndex.Type].RayTest(child.ShapeIndex.Index, child.AsPose(), localRay, ref maximumT, pool, ref tester);
+ if (tester.T >= 0)
{
- ref var child = ref Children[i];
- CompoundChildShapeTester tester;
- tester.T = -1;
- tester.Normal = default;
- shapeBatches[child.ShapeIndex.Type].RayTest(child.ShapeIndex.Index, child.LocalPose, localRay, ref maximumT, ref tester);
- if (tester.T >= 0)
- {
- Debug.Assert(maximumT >= tester.T, "Whatever generated this ray hit should have obeyed the current maximumT value.");
- Matrix3x3.Transform(tester.Normal, orientation, out var rotatedNormal);
- hitHandler.OnRayHit(ray, ref maximumT, tester.T, rotatedNormal, i);
- }
+ Debug.Assert(maximumT >= tester.T, "Whatever generated this ray hit should have obeyed the current maximumT value.");
+ Matrix3x3.Transform(tester.Normal, orientation, out var rotatedNormal);
+ hitHandler.OnRayHit(ray, ref maximumT, tester.T, rotatedNormal, i);
}
}
}
+ }
- public unsafe void RayTest(in RigidPose pose, ref RaySource rays, Shapes shapeBatches, ref TRayHitHandler hitHandler) where TRayHitHandler : struct, IShapeRayHitHandler
+ public unsafe void RayTest(in RigidPose pose, ref RaySource rays, Shapes shapeBatches, BufferPool pool, ref TRayHitHandler hitHandler) where TRayHitHandler : struct, IShapeRayHitHandler
+ {
+ //TODO: Note that we dispatch a bunch of scalar tests here. You could be more clever than this- batched tests are possible.
+ //It's relatively easy to do batching for this compound type since there is no hierarchy traversal, but we refactored things to avoid an infinite generic expansion issue in AOT compilation.
+ //There are plenty of ways to work around that, but right now our batched raytracing implementation is bad enough that spending extra work here is questionable. We'll avoid breaking it for now, but that's all.
+ for (int i = 0; i < rays.RayCount; ++i)
{
- //TODO: Note that we dispatch a bunch of scalar tests here. You could be more clever than this- batched tests are possible.
- //It's relatively easy to do batching for this compound type since there is no hierarchy traversal, but we refactored things to avoid an infinite generic expansion issue in AOT compilation.
- //There are plenty of ways to work around that, but right now our batched raytracing implementation is bad enough that spending extra work here is questionable. We'll avoid breaking it for now, but that's all.
- for (int i = 0; i < rays.RayCount; ++i)
- {
- rays.GetRay(i, out var ray, out var maximumT);
- RayTest(pose, *ray, ref *maximumT, shapeBatches, ref hitHandler);
- }
+ rays.GetRay(i, out var ray, out var maximumT);
+ RayTest(pose, *ray, ref *maximumT, shapeBatches, pool, ref hitHandler);
}
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapes)
+ {
+ return new CompoundShapeBatch(pool, initialCapacity, shapes);
+ }
+ public int ChildCount
+ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapes)
- {
- return new CompoundShapeBatch(pool, initialCapacity, shapes);
- }
+ get { return Children.Length; }
+ }
- public int ChildCount
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get { return Children.Length; }
- }
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref CompoundChild GetChild(int compoundChildIndex)
+ {
+ return ref Children[compoundChildIndex];
+ }
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public ref CompoundChild GetChild(int compoundChildIndex)
+ ///
+ /// Adds a child to the compound.
+ ///
+ /// Child to add to the compound.
+ /// Pool to use to resize the compound's children buffer if necessary.
+ public void Add(CompoundChild child, BufferPool pool)
+ {
+ pool.Resize(ref Children, Children.Length + 1, Children.Length);
+ Children[^1] = child;
+ }
+
+ ///
+ /// Removes a child from the compound by index. The last child is pulled to fill the gap left by the removed child.
+ ///
+ /// Index of the child to remove from the compound.
+ /// Pool to use to resize the compound's children buffer if necessary.
+ public void RemoveAt(int childIndex, BufferPool pool)
+ {
+ var lastIndex = Children.Length - 1;
+ if (childIndex < lastIndex)
{
- return ref Children[compoundChildIndex];
+ Children[childIndex] = Children[lastIndex];
}
+ //Shrinking the buffer takes care of 'removing' the now-empty last slot.
+ pool.Resize(ref Children, Children.Length - 1, Children.Length - 1);
+ }
+
- public unsafe void FindLocalOverlaps(ref Buffer pairs, BufferPool pool, Shapes shapes, ref TOverlaps overlaps)
- where TOverlaps : struct, ICollisionTaskOverlaps
- where TSubpairOverlaps : struct, ICollisionTaskSubpairOverlaps
+ public unsafe void FindLocalOverlaps(ref Buffer pairs, BufferPool pool, Shapes shapes, ref TOverlaps overlaps)
+ where TOverlaps : struct, ICollisionTaskOverlaps
+ where TSubpairOverlaps : struct, ICollisionTaskSubpairOverlaps
+ {
+ for (int pairIndex = 0; pairIndex < pairs.Length; ++pairIndex)
{
- for (int pairIndex = 0; pairIndex < pairs.Length; ++pairIndex)
+ ref var pair = ref pairs[pairIndex];
+ ref var compound = ref Unsafe.AsRef(pair.Container);
+ ref var overlapsForPair = ref overlaps.GetOverlapsForPair(pairIndex);
+ for (int i = 0; i < compound.Children.Length; ++i)
{
- ref var pair = ref pairs[pairIndex];
- ref var compound = ref Unsafe.AsRef(pair.Container);
- ref var overlapsForPair = ref overlaps.GetOverlapsForPair(pairIndex);
- for (int i = 0; i < compound.Children.Length; ++i)
+ ref var child = ref compound.Children[i];
+ //TODO: This does quite a bit of work. May want to try a simple bounding sphere instead (based on a dedicated maximum radius request).
+ //Could also benefit from using the BoundingBox layout test, which is a little faster than 4 independent values.
+ shapes[child.ShapeIndex.Type].ComputeBounds(child.ShapeIndex.Index, child.LocalOrientation, out _, out _, out var min, out var max);
+ min += child.LocalPosition;
+ max += child.LocalPosition;
+ if (BoundingBox.Intersects(min, max, pair.Min, pair.Max))
{
- ref var child = ref compound.Children[i];
- //TODO: This does quite a bit of work. May want to try a simple bounding sphere instead (based on a dedicated maximum radius request).
- shapes[child.ShapeIndex.Type].ComputeBounds(child.ShapeIndex.Index, child.LocalPose.Orientation, out _, out _, out var min, out var max);
- min += child.LocalPose.Position;
- max += child.LocalPose.Position;
- if (BoundingBox.Intersects(min, max, pair.Min, pair.Max))
- {
- overlapsForPair.Allocate(pool) = i;
- }
+ overlapsForPair.Allocate(pool) = i;
}
}
}
+ }
- public unsafe void FindLocalOverlaps(in Vector3 min, in Vector3 max, in Vector3 sweep, float maximumT, BufferPool pool, Shapes shapes, void* overlapsPointer)
- where TOverlaps : ICollisionTaskSubpairOverlaps
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void FindLocalOverlaps(Vector3 min, Vector3 max, BufferPool pool, Shapes shapes, ref TEnumerator enumerator)
+ where TEnumerator : IBreakableForEach
+ {
+ for (int i = 0; i < Children.Length; ++i)
{
- Tree.ConvertBoxToCentroidWithExtent(min, max, out var sweepOrigin, out var expansion);
- TreeRay.CreateFrom(sweepOrigin, sweep, maximumT, out var ray);
- ref var overlaps = ref Unsafe.AsRef(overlapsPointer);
- for (int i = 0; i < Children.Length; ++i)
+ ref var child = ref Children[i];
+ shapes[child.ShapeIndex.Type].ComputeBounds(child.ShapeIndex.Index, child.LocalOrientation, out _, out _, out var childMin, out var childMax);
+ childMin += child.LocalPosition;
+ childMax += child.LocalPosition;
+ if (BoundingBox.Intersects(childMin, childMax, min, max))
{
- ref var child = ref Children[i];
- shapes[child.ShapeIndex.Type].ComputeBounds(child.ShapeIndex.Index, child.LocalPose.Orientation, out _, out _, out var childMin, out var childMax);
- childMin = childMin + child.LocalPose.Position - expansion;
- childMax = childMax + child.LocalPose.Position + expansion;
- if (Tree.Intersects(childMin, childMax, &ray, out _))
- {
- overlaps.Allocate(pool) = i;
- }
+ if (!enumerator.LoopBody(i))
+ return;
}
-
}
+ }
- public void Dispose(BufferPool bufferPool)
+ public unsafe void FindLocalOverlaps(Vector3 min, Vector3 max, Vector3 sweep, float maximumT, BufferPool pool, Shapes shapes, void* overlapsPointer)
+ where TOverlaps : ICollisionTaskSubpairOverlaps
+ {
+ Tree.ConvertBoxToCentroidWithExtent(min, max, out var sweepOrigin, out var expansion);
+ TreeRay.CreateFrom(sweepOrigin, sweep, maximumT, out var ray);
+ ref var overlaps = ref Unsafe.AsRef(overlapsPointer);
+ for (int i = 0; i < Children.Length; ++i)
{
- bufferPool.Return(ref Children);
+ ref var child = ref Children[i];
+ shapes[child.ShapeIndex.Type].ComputeBounds(child.ShapeIndex.Index, child.LocalOrientation, out _, out _, out var childMin, out var childMax);
+ childMin = childMin + child.LocalPosition - expansion;
+ childMax = childMax + child.LocalPosition + expansion;
+ if (Tree.Intersects(childMin, childMax, &ray, out _))
+ {
+ overlaps.Allocate(pool) = i;
+ }
}
+ }
+
+ ///
+ /// Computes the inertia of a compound. Does not recenter the child poses.
+ ///
+ /// Masses of the children.
+ /// Shapes collection containing the data for the compound child shapes.
+ /// Inertia of the compound.
+ public BodyInertia ComputeInertia(Span childMasses, Shapes shapes)
+ {
+ return CompoundBuilder.ComputeInertia(Children, childMasses, shapes);
+ }
- ///
- /// Type id of list based compound shapes.
- ///
- public const int Id = 6;
- public int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
+ ///
+ /// Computes the inertia of a compound. Recenters the child poses around the calculated center of mass.
+ ///
+ /// Shapes collection containing the data for the compound child shapes.
+ /// Masses of the children.
+ /// Calculated center of mass of the compound. Subtracted from all the compound child poses.
+ /// Inertia of the compound.
+ public BodyInertia ComputeInertia(Span childMasses, Shapes shapes, out Vector3 centerOfMass)
+ {
+ return CompoundBuilder.ComputeInertia(Children, childMasses, shapes, out centerOfMass);
}
+ public void Dispose(BufferPool bufferPool)
+ {
+ bufferPool.Return(ref Children);
+ }
+ ///
+ /// Type id of list based compound shapes.
+ ///
+ public const int Id = 6;
+ public static int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
}
diff --git a/BepuPhysics/Collidables/CompoundBuilder.cs b/BepuPhysics/Collidables/CompoundBuilder.cs
new file mode 100644
index 000000000..ad0268c7d
--- /dev/null
+++ b/BepuPhysics/Collidables/CompoundBuilder.cs
@@ -0,0 +1,489 @@
+using BepuUtilities;
+using BepuUtilities.Collections;
+using BepuUtilities.Memory;
+using System;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+namespace BepuPhysics.Collidables
+{
+ ///
+ /// Reusable convenience type for incrementally building compound shapes.
+ ///
+ public struct CompoundBuilder : IDisposable
+ {
+ public BufferPool Pool;
+ public Shapes Shapes;
+
+ public struct Child
+ {
+ public RigidPose LocalPose;
+ public TypedIndex ShapeIndex;
+ ///
+ /// Weight associated with this child. Acts as the child's mass when interpreted as a dynamic compound.
+ /// When interpreted as kinematic with recentering, it is used as a local pose weight to compute the center of rotation.
+ ///
+ public float Weight;
+ ///
+ /// Inverse inertia tensor of the child in its local space.
+ ///
+ public Symmetric3x3 LocalInverseInertia;
+ }
+
+ public QuickList Children;
+
+ ///
+ /// Creates a compound builder.
+ ///
+ /// Buffer pool to allocate memory from when necessary.
+ /// Shapes collection to access when constructing the compound children.
+ /// Number of children the compound builder can hold without resizing.
+ public CompoundBuilder(BufferPool pool, Shapes shapes, int initialBuilderCapacity)
+ {
+ Pool = pool;
+ Shapes = shapes;
+ Children = new QuickList(initialBuilderCapacity, Pool);
+ }
+
+ ///
+ /// Adds a new shape to the accumulator.
+ ///
+ /// Index of the shape to add.
+ /// Pose of the shape in the compound's local space.
+ /// Weight of the shape. If the compound is interpreted as a dynamic, this will be used as the mass. Otherwise, it is used for recentering.
+ /// Inverse inertia tensor of the shape being added in its local space. This is assumed to already be scaled as desired by the weight.
+ public void Add(TypedIndex shape, in RigidPose localPose, in Symmetric3x3 localInverseInertia, float weight)
+ {
+ Debug.Assert(Compound.ValidateChildIndex(shape, Shapes));
+ ref var child = ref Children.Allocate(Pool);
+ child.LocalPose = localPose;
+ child.ShapeIndex = shape;
+ child.Weight = weight;
+ child.LocalInverseInertia = localInverseInertia;
+ //This assumes the given inertia is nonsingular. That should be a valid assumption, unless the user is trying to supply an axis-locked tensor.
+ //For such a use case, it's best to just lock the axis after computing a 'normal' inertia.
+ Debug.Assert(Symmetric3x3.Determinant(localInverseInertia) > 0,
+ "Child inertia tensors should be invertible. If making an axis-locked compound, consider locking the axis on the completed inertia. " +
+ "If making a kinematic, consider using the overload which takes no inverse inertia.");
+ }
+
+ ///
+ /// Adds a new shape to the accumulator.
+ ///
+ /// Index of the shape to add.
+ /// Pose of the shape in the compound's local space.
+ /// Inverse inertia tensor and inverse mass of the shape being added in the child's local space. The inverse mass is used as the inverse weight for building the compound.
+ public void Add(TypedIndex shape, in RigidPose localPose, in BodyInertia childInertia)
+ {
+ Debug.Assert(childInertia.InverseMass > 0, "Child masses should be finite.");
+ Add(shape, localPose, childInertia.InverseInertiaTensor, 1f / childInertia.InverseMass);
+ }
+
+ ///
+ /// Adds a new shape to the accumulator, assuming it has infinite inertia.
+ ///
+ /// Index of the shape to add.
+ /// Pose of the shape in the compound's local space.
+ /// Weight of the shape used for computing the center of rotation.
+ public void AddForKinematic(TypedIndex shape, in RigidPose localPose, float weight)
+ {
+ Debug.Assert(Compound.ValidateChildIndex(shape, Shapes));
+ ref var child = ref Children.Allocate(Pool);
+ child.LocalPose = localPose;
+ child.ShapeIndex = shape;
+ child.Weight = weight;
+ child.LocalInverseInertia = default;
+ }
+
+ ///
+ /// Adds a new shape to the accumulator, creating a new shape in the shapes set. The mass used to compute the inertia tensor will be based on the given weight.
+ ///
+ /// Type of the shape to add to the accumulator and the shapes set.
+ /// Shape to add.
+ /// Pose of the shape in the compound's local space.
+ /// Weight of the shape. If the compound is interpreted as a dynamic, this will be used as the mass and scales the inertia tensor.
+ /// Otherwise, it is used for recentering.
+ public void Add(in TShape shape, in RigidPose localPose, float weight) where TShape : unmanaged, IConvexShape
+ {
+ Add(Shapes.Add(shape), localPose, shape.ComputeInertia(weight).InverseInertiaTensor, weight);
+ }
+
+ ///
+ /// Adds a new shape to the accumulator, creating a new shape in the shapes set. Inertia is assumed to be infinite.
+ ///
+ /// Type of the shape to add to the accumulator and the shapes set.
+ /// Shape to add.
+ /// Pose of the shape in the compound's local space.
+ /// Weight of the shape. If the compound is interpreted as a dynamic, this will be used as the mass. Otherwise, it is used for recentering.
+ public void AddForKinematic(in TShape shape, in RigidPose localPose, float weight) where TShape : unmanaged, IConvexShape
+ {
+ AddForKinematic(Shapes.Add(shape), localPose, weight);
+ }
+
+
+ ///
+ /// Gets the contribution to an inertia tensor of a point mass at the given offset from the center of mass.
+ ///
+ /// Offset from the center of mass.
+ /// Mass of the point.
+ /// Contribution to the inertia tensor.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void GetOffsetInertiaContribution(Vector3 offset, float mass, out Symmetric3x3 contribution)
+ {
+ var innerProduct = Vector3.Dot(offset, offset);
+ contribution.XX = mass * (innerProduct - offset.X * offset.X);
+ contribution.YX = -mass * (offset.Y * offset.X);
+ contribution.YY = mass * (innerProduct - offset.Y * offset.Y);
+ contribution.ZX = -mass * (offset.Z * offset.X);
+ contribution.ZY = -mass * (offset.Z * offset.Y);
+ contribution.ZZ = mass * (innerProduct - offset.Z * offset.Z);
+ }
+
+ ///
+ /// Builds a buffer of compound children from the accumulated set for a dynamic compound.
+ /// Computes a center of mass and recenters child shapes relative to it. Does not reset the accumulator.
+ ///
+ /// List of children created from the accumulated set.
+ /// Combined inertia of the compound.
+ /// Computed center of rotation based on the poses and weights of accumulated children.
+ public void BuildDynamicCompound(out Buffer children, out BodyInertia inertia, out Vector3 center)
+ {
+ center = new Vector3();
+ float totalWeight = 0;
+ for (int i = 0; i < Children.Count; ++i)
+ {
+ center += Children[i].LocalPose.Position * Children[i].Weight;
+ totalWeight += Children[i].Weight;
+ }
+ Debug.Assert(totalWeight > 0, "The compound as a whole must have nonzero weight when using a recentering build. The center is undefined.");
+
+ inertia.InverseMass = 1f / totalWeight;
+ center *= inertia.InverseMass;
+ Pool.Take(Children.Count, out children);
+ Symmetric3x3 summedInertia = default;
+ for (int i = 0; i < Children.Count; ++i)
+ {
+ ref var sourceChild = ref Children[i];
+ ref var targetChild = ref children[i];
+ targetChild.LocalPosition = sourceChild.LocalPose.Position - center;
+ targetChild.LocalOrientation = sourceChild.LocalPose.Orientation;
+ targetChild.ShapeIndex = sourceChild.ShapeIndex;
+ Symmetric3x3.Add(ComputeInertiaForChild(targetChild.LocalPosition, targetChild.LocalOrientation, sourceChild.LocalInverseInertia, sourceChild.Weight), summedInertia, out summedInertia);
+ }
+ Symmetric3x3.Invert(summedInertia, out inertia.InverseInertiaTensor);
+ }
+
+ ///
+ /// Builds a buffer of compound children from the accumulated set for a dynamic compound. Does not recenter the children. Does not reset the accumulator.
+ ///
+ /// List of children created from the accumulated set.
+ /// Combined inertia of the compound.
+ public void BuildDynamicCompound(out Buffer children, out BodyInertia inertia)
+ {
+ float totalWeight = 0;
+ for (int i = 0; i < Children.Count; ++i)
+ {
+ totalWeight += Children[i].Weight;
+ }
+ Debug.Assert(totalWeight > 0, "The compound as a whole must have nonzero weight when creating a dynamic compound.");
+
+ inertia.InverseMass = 1f / totalWeight;
+ Pool.Take(Children.Count, out children);
+ Symmetric3x3 summedInertia = default;
+ for (int i = 0; i < Children.Count; ++i)
+ {
+ ref var sourceChild = ref Children[i];
+ ref var targetChild = ref children[i];
+ targetChild.LocalPosition = sourceChild.LocalPose.Position;
+ targetChild.LocalOrientation = sourceChild.LocalPose.Orientation;
+ targetChild.ShapeIndex = sourceChild.ShapeIndex;
+ Symmetric3x3.Add(ComputeInertiaForChild(sourceChild.LocalPose.Position, sourceChild.LocalPose.Orientation, sourceChild.LocalInverseInertia, sourceChild.Weight), summedInertia, out summedInertia);
+ }
+ Symmetric3x3.Invert(summedInertia, out inertia.InverseInertiaTensor);
+ }
+
+ ///
+ /// Computes the uninverted inertia contribution of a child.
+ ///
+ /// Pose of the child.
+ /// Inverse inertia tensor of the child in its local space.
+ /// Mass of the child.
+ /// Inertia contribution of the child to a compound given its relative pose.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Symmetric3x3 ComputeInertiaForChild(in RigidPose pose, Symmetric3x3 inverseLocalInertia, float mass)
+ {
+ return ComputeInertiaForChild(pose.Position, pose.Orientation, inverseLocalInertia, mass);
+ }
+ ///
+ /// Computes the uninverted inertia contribution of a child.
+ ///
+ /// Position of the child.
+ /// Orientation of the child.
+ /// Inverse inertia tensor of the child in its local space.
+ /// Mass of the child.
+ /// Inertia contribution of the child to a compound given its relative pose.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Symmetric3x3 ComputeInertiaForChild(Vector3 position, Quaternion orientation, Symmetric3x3 inverseLocalInertia, float mass)
+ {
+ GetOffsetInertiaContribution(position, mass, out var offsetContribution);
+ //This assumes the given inertia is nonsingular. That should be a valid assumption, unless the user is trying to supply an axis-locked tensor.
+ //For such a use case, it's best to just lock the axis after computing a 'normal' inertia.
+ Debug.Assert(Symmetric3x3.Determinant(inverseLocalInertia) > 0,
+ "Child inertia tensors should be invertible. If making an axis-locked compound, consider locking the axis on the completed inertia. " +
+ "If making a kinematic, consider using the overload which takes no inverse inertia.");
+ PoseIntegration.RotateInverseInertia(inverseLocalInertia, orientation, out var rotatedInverseInertia);
+ Symmetric3x3.Invert(rotatedInverseInertia, out var inertia);
+ Symmetric3x3.Add(offsetContribution, inertia, out inertia);
+ return inertia;
+ }
+
+ ///
+ /// Computes the inertia for a set of compound children based on their poses and the provided inverse inertias. Does not recenter the children.
+ ///
+ /// Children and their associated poses.
+ /// Inverse inertias of the children, each in the child's local space. Assumed to have already been premultiplied by the mass of the child.
+ /// Masses of each child in the compound.
+ /// of the compound.
+ public static BodyInertia ComputeInverseInertia(Span children, Span inverseLocalInertias, Span childMasses)
+ {
+ Symmetric3x3 summedInertia = default;
+ float massSum = 0;
+ for (int i = 0; i < children.Length; ++i)
+ {
+ ref var child = ref children[i];
+ summedInertia += ComputeInertiaForChild(child.LocalPosition, child.LocalOrientation, inverseLocalInertias[i], childMasses[i]);
+ massSum += childMasses[i];
+ }
+ BodyInertia inertia;
+ Symmetric3x3.Invert(summedInertia, out inertia.InverseInertiaTensor);
+ inertia.InverseMass = 1f / massSum;
+ return inertia;
+ }
+
+ ///
+ /// Computes the inverse inertia for a set of compound children based on their poses and the provided inverse inertias. Does not recenter the children.
+ ///
+ /// Poses of the compound's children.
+ /// Inverse inertias of the children, each in the child's local space. Assumed to have already been premultiplied by the mass of the child.
+ /// Masses of each child in the compound.
+ /// of the compound.
+ public static BodyInertia ComputeInverseInertia(Span childPoses, Span inverseLocalInertias, Span childMasses)
+ {
+ Symmetric3x3 summedInertia = default;
+ float massSum = 0;
+ for (int i = 0; i < childPoses.Length; ++i)
+ {
+ summedInertia += ComputeInertiaForChild(childPoses[i], inverseLocalInertias[i], childMasses[i]);
+ massSum += childMasses[i];
+ }
+ BodyInertia inertia;
+ Symmetric3x3.Invert(summedInertia, out inertia.InverseInertiaTensor);
+ inertia.InverseMass = 1f / massSum;
+ return inertia;
+ }
+ ///
+ /// Computes the center of mass of a compound.
+ ///
+ /// Children of the compound.
+ /// Masses of the children in the compound.
+ /// Inverse of the sum of all child masses.
+ /// The compound's center of mass.
+ public static Vector3 ComputeCenterOfMass(Span children, Span childMasses, out float inverseMass)
+ {
+ Vector3 sum = default;
+ float massSum = 0;
+ for (int i = 0; i < children.Length; ++i)
+ {
+ sum += childMasses[i] * children[i].LocalPosition;
+ massSum += childMasses[i];
+ }
+ inverseMass = 1f / massSum;
+ return sum * inverseMass;
+ }
+ ///
+ /// Computes the center of mass of a compound.
+ ///
+ /// Poses of the children in the compound.
+ /// Masses of the children in the compound.
+ /// Inverse of the sum of all child masses.
+ /// The compound's center of mass.
+ public static Vector3 ComputeCenterOfMass(Span childPoses, Span childMasses, out float inverseMass)
+ {
+ Vector3 sum = default;
+ float massSum = 0;
+ for (int i = 0; i < childPoses.Length; ++i)
+ {
+ sum += childMasses[i] * childPoses[i].Position;
+ massSum += childMasses[i];
+ }
+ inverseMass = 1f / massSum;
+ return sum * inverseMass;
+ }
+ ///
+ /// Computes the center of mass of a compound.
+ ///
+ /// Children of the compound.
+ /// Masses of the children in the compound.
+ /// The compound's center of mass.
+ public static Vector3 ComputeCenterOfMass(Span children, Span childMasses) => ComputeCenterOfMass(children, childMasses, out _);
+
+ ///
+ /// Computes the center of mass of a compound.
+ ///
+ /// Poses of the children in the compound.
+ /// Masses of the children in the compound.
+ /// The compound's center of mass.
+ public static Vector3 ComputeCenterOfMass(Span childPoses, Span childMasses) => ComputeCenterOfMass(childPoses, childMasses, out _);
+
+ ///
+ /// Computes the inertia for a set of compound children based on their poses and the provided inverse inertias. Recenters the children onto the computed center of mass.
+ ///
+ /// Children and their associated poses. Center of mass will be subtracted from the child position.
+ /// Inverse inertias of the children, each in the child's local space. Assumed to have already been premultiplied by the mass of the child.
+ /// Masses of each child in the compound.
+ /// Computed center of mass that was subtracted from the child positions.
+ /// of the compound.
+ public static BodyInertia ComputeInverseInertia(Span children, Span inverseLocalInertias, Span childMasses, out Vector3 centerOfMass)
+ {
+ Symmetric3x3 summedInertia = default;
+ BodyInertia inertia;
+ centerOfMass = ComputeCenterOfMass(children, childMasses, out inertia.InverseMass);
+ for (int i = 0; i < children.Length; ++i)
+ {
+ ref var child = ref children[i];
+ child.LocalPosition -= centerOfMass;
+ summedInertia += ComputeInertiaForChild(child.LocalPosition, child.LocalOrientation, inverseLocalInertias[i], childMasses[i]);
+ }
+ Symmetric3x3.Invert(summedInertia, out inertia.InverseInertiaTensor);
+ return inertia;
+ }
+ ///
+ /// Computes the inertia for a set of compound children based on their poses and the provided inverse inertias. Recenters the children onto the computed center of mass.
+ ///
+ /// Poses of the compound's children. Center of mass will be subtracted from the child position.
+ /// Inverse inertias of the children, each in the child's local space. Assumed to have already been premultiplied by the mass of the child.
+ /// Masses of each child in the compound.
+ /// Computed center of mass that was subtracted from the child positions.
+ /// of the compound.
+ public static BodyInertia ComputeInverseInertia(Span childPoses, Span inverseLocalInertias, Span childMasses, out Vector3 centerOfMass)
+ {
+ Symmetric3x3 summedInertia = default;
+ BodyInertia inertia;
+ centerOfMass = ComputeCenterOfMass(childPoses, childMasses, out inertia.InverseMass);
+ for (int i = 0; i < childPoses.Length; ++i)
+ {
+ childPoses[i].Position -= centerOfMass;
+ summedInertia += ComputeInertiaForChild(childPoses[i], inverseLocalInertias[i], childMasses[i]);
+ }
+ Symmetric3x3.Invert(summedInertia, out inertia.InverseInertiaTensor);
+ return inertia;
+ }
+
+ ///
+ /// Computes the inertia of a compound. Does not recenter the child poses.
+ ///
+ /// Children of the compound.
+ /// Shapes collection containing the data for the compound child shapes.
+ /// Masses of the children.
+ /// Inertia of the compound.
+ public static BodyInertia ComputeInertia(Span children, Span childMasses, Shapes shapes)
+ {
+ Span localInverseInertias = stackalloc Symmetric3x3[children.Length];
+ for (int i = 0; i < children.Length; ++i)
+ {
+ ref var child = ref children[i];
+ if (shapes[child.ShapeIndex.Type] is IConvexShapeBatch batch)
+ {
+ localInverseInertias[i] = batch.ComputeInertia(child.ShapeIndex.Index, childMasses[i]).InverseInertiaTensor;
+ }
+ }
+ return ComputeInverseInertia(children, localInverseInertias, childMasses);
+ }
+
+ ///
+ /// Computes the inertia of a compound. Recenters the child poses around the calculated center of mass.
+ ///
+ /// Children of the compound. Child local positions will have the calculated center of mass subtracted from them.
+ /// Shapes collection containing the data for the compound child shapes.
+ /// Masses of the children.
+ /// Calculated center of mass of the compound. Subtracted from all the compound child poses.
+ /// Inertia of the compound.
+ public static BodyInertia ComputeInertia(Span children, Span childMasses, Shapes shapes, out Vector3 centerOfMass)
+ {
+ Span localInverseInertias = stackalloc Symmetric3x3[children.Length];
+ for (int i = 0; i < children.Length; ++i)
+ {
+ ref var child = ref children[i];
+ if (shapes[child.ShapeIndex.Type] is IConvexShapeBatch batch)
+ {
+ localInverseInertias[i] = batch.ComputeInertia(child.ShapeIndex.Index, childMasses[i]).InverseInertiaTensor;
+ }
+ }
+ return ComputeInverseInertia(children, localInverseInertias, childMasses, out centerOfMass);
+ }
+
+ ///
+ /// Builds a buffer of compound children from the accumulated set for a kinematic compound.
+ /// Computes a center of mass and recenters child shapes relative to it. Does not reset the accumulator.
+ ///
+ /// List of children created from the accumulated set.
+ /// Computed center of rotation based on the poses and weights of accumulated children.
+ public void BuildKinematicCompound(out Buffer children, out Vector3 center)
+ {
+ center = new Vector3();
+ float totalWeight = 0;
+ for (int i = 0; i < Children.Count; ++i)
+ {
+ center += Children[i].LocalPose.Position * Children[i].Weight;
+ totalWeight += Children[i].Weight;
+ }
+ Debug.Assert(totalWeight > 0, "The compound as a whole must have nonzero weight when using a recentering build. The center is undefined.");
+
+ var inverseWeight = 1f / totalWeight;
+ center *= inverseWeight;
+ Pool.Take(Children.Count, out children);
+ for (int i = 0; i < Children.Count; ++i)
+ {
+ ref var sourceChild = ref Children[i];
+ ref var targetChild = ref children[i];
+ targetChild.LocalPosition = sourceChild.LocalPose.Position - center;
+ targetChild.LocalOrientation = sourceChild.LocalPose.Orientation;
+ targetChild.ShapeIndex = sourceChild.ShapeIndex;
+ }
+ }
+
+ ///
+ /// Builds a buffer of compound children from the accumulated set for a kinematic compound. Does not recenter children. Does not reset the accumulator.
+ ///
+ /// List of children created from the accumulated set.
+ public void BuildKinematicCompound(out Buffer children)
+ {
+ Pool.Take(Children.Count, out children);
+ for (int i = 0; i < Children.Count; ++i)
+ {
+ ref var sourceChild = ref Children[i];
+ ref var targetChild = ref children[i];
+ targetChild.LocalPosition = sourceChild.LocalPose.Position;
+ targetChild.LocalOrientation = sourceChild.LocalPose.Orientation;
+ targetChild.ShapeIndex = sourceChild.ShapeIndex;
+ }
+ }
+
+ ///
+ /// Empties out the accumulated children.
+ ///
+ public void Reset()
+ {
+ Children.Count = 0;
+ }
+
+ ///
+ /// Returns internal resources to the pool, rendering the builder unusable.
+ ///
+ public void Dispose()
+ {
+ Children.Dispose(Pool);
+ }
+ }
+}
diff --git a/BepuPhysics/Collidables/CompoundHelpers.cs b/BepuPhysics/Collidables/CompoundHelpers.cs
deleted file mode 100644
index d300b86c7..000000000
--- a/BepuPhysics/Collidables/CompoundHelpers.cs
+++ /dev/null
@@ -1,262 +0,0 @@
-using BepuUtilities;
-using BepuUtilities.Collections;
-using BepuUtilities.Memory;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Text;
-
-namespace BepuPhysics.Collidables
-{
- ///
- /// Reusable convenience type for incrementally building compound shapes.
- ///
- public struct CompoundBuilder : IDisposable
- {
- public BufferPool Pool;
- public Shapes Shapes;
-
- public struct Child
- {
- public RigidPose LocalPose;
- public TypedIndex ShapeIndex;
- ///
- /// Weight associated with this child. Acts as the child's mass when interpreted as a dynamic compound.
- /// When interpreted as kinematic with recentering, it is used as a local pose weight to compute the center of rotation.
- ///
- public float Weight;
- ///
- /// Inertia tensor associated with the child. If inertia is all zeroes, it is interpreted as infinite.
- ///
- public Symmetric3x3 Inertia;
- }
-
- public QuickList Children;
-
- public CompoundBuilder(BufferPool pool, Shapes shapes, int builderCapacity)
- {
- Pool = pool;
- Shapes = shapes;
- Children = new QuickList(builderCapacity, Pool);
- }
-
- ///
- /// Adds a new shape to the accumulator, creating a new shape in the shapes set. The mass used to compute the inertia tensor will be based on the given weight.
- ///
- /// Type of the shape to add to the accumulator and the shapes set.
- /// Shape to add.
- /// Pose of the shape in the compound's local space.
- /// Weight of the shape. If the compound is interpreted as a dynamic, this will be used as the mass and scales the inertia tensor.
- /// Otherwise, it is used for recentering.
- public void Add(in TShape shape, in RigidPose localPose, float weight) where TShape : unmanaged, IConvexShape
- {
- ref var child = ref Children.Allocate(Pool);
- child.LocalPose = localPose;
- child.ShapeIndex = Shapes.Add(shape);
- child.Weight = weight;
- shape.ComputeInertia(weight, out var inertia);
- Symmetric3x3.Invert(inertia.InverseInertiaTensor, out child.Inertia);
- }
-
- ///
- /// Adds a new shape to the accumulator, creating a new shape in the shapes set. Inertia is assumed to be infinite.
- ///
- /// Type of the shape to add to the accumulator and the shapes set.
- /// Shape to add.
- /// Pose of the shape in the compound's local space.
- /// Weight of the shape. If the compound is interpreted as a dynamic, this will be used as the mass. Otherwise, it is used for recentering.
- public void AddForKinematic(in TShape shape, in RigidPose localPose, float weight) where TShape : unmanaged, IConvexShape
- {
- ref var child = ref Children.Allocate(Pool);
- child.LocalPose = localPose;
- child.ShapeIndex = Shapes.Add(shape);
- child.Weight = weight;
- child.Inertia = default;
- }
-
- ///
- /// Adds a new shape to the accumulator.
- ///
- /// Index of the shape to add.
- /// Pose of the shape in the compound's local space.
- /// Weight of the shape. If the compound is interpreted as a dynamic, this will be used as the mass. Otherwise, it is used for recentering.
- /// Inverse inertia tensor of the shape being added. This is assumed to already be scaled as desired by the weight.
- public void Add(TypedIndex shape, in RigidPose localPose, in Symmetric3x3 inverseInertia, float weight)
- {
- Debug.Assert(Compound.ValidateChildIndex(shape, Shapes));
- ref var child = ref Children.Allocate(Pool);
- child.LocalPose = localPose;
- child.ShapeIndex = shape;
- child.Weight = weight;
- //This assumes the given inertia is nonsingular. That should be a valid assumption, unless the user is trying to supply an axis-locked tensor.
- //For such a use case, it's best to just lock the axis after computing a 'normal' inertia.
- Debug.Assert(Symmetric3x3.Determinant(inverseInertia) > 0,
- "Shape inertia tensors should be invertible. If making an axis-locked compound, consider locking the axis on the completed inertia. " +
- "If making a kinematic, consider using the overload which takes no inverse inertia.");
- Symmetric3x3.Invert(inverseInertia, out child.Inertia);
- }
-
- ///
- /// Adds a new shape to the accumulator, assuming it has infinite inertia.
- ///
- /// Index of the shape to add.
- /// Pose of the shape in the compound's local space.
- /// Weight of the shape used for computing the center of rotation.
- public void AddForKinematic(TypedIndex shape, in RigidPose localPose, float weight)
- {
- Debug.Assert(Compound.ValidateChildIndex(shape, Shapes));
- ref var child = ref Children.Allocate(Pool);
- child.LocalPose = localPose;
- child.ShapeIndex = shape;
- child.Weight = weight;
- child.Inertia = default;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void GetOffsetInertiaContribution(in Vector3 offset, float mass, out Symmetric3x3 contribution)
- {
- var innerProduct = Vector3.Dot(offset, offset);
- contribution.XX = mass * (innerProduct - offset.X * offset.X);
- contribution.YX = -mass * (offset.Y * offset.X);
- contribution.YY = mass * (innerProduct - offset.Y * offset.Y);
- contribution.ZX = -mass * (offset.Z * offset.X);
- contribution.ZY = -mass * (offset.Z * offset.Y);
- contribution.ZZ = mass * (innerProduct - offset.Z * offset.Z);
- }
-
- ///
- /// Builds a buffer of compound children from the accumulated set for a dynamic compound.
- /// Computes a center of mass and recenters child shapes relative to it. Does not reset the accumulator.
- ///
- /// List of children created from the accumulated set.
- /// Combined inertia of the compound.
- /// Computed center of rotation based on the poses and weights of accumulated children.
- public void BuildDynamicCompound(out Buffer children, out BodyInertia inertia, out Vector3 center)
- {
- center = new Vector3();
- float totalWeight = 0;
- for (int i = 0; i < Children.Count; ++i)
- {
- center += Children[i].LocalPose.Position * Children[i].Weight;
- totalWeight += Children[i].Weight;
- }
- Debug.Assert(totalWeight > 0, "The compound as a whole must have nonzero weight when using a recentering build. The center is undefined.");
-
- inertia.InverseMass = 1f / totalWeight;
- center *= inertia.InverseMass;
- Pool.Take(Children.Count, out children);
- Symmetric3x3 summedInertia = default;
- for (int i = 0; i < Children.Count; ++i)
- {
- ref var sourceChild = ref Children[i];
- ref var targetChild = ref children[i];
- targetChild.LocalPose.Position = sourceChild.LocalPose.Position - center;
- GetOffsetInertiaContribution(targetChild.LocalPose.Position, sourceChild.Weight, out var contribution);
- Symmetric3x3.Add(contribution, summedInertia, out summedInertia);
- Symmetric3x3.Add(summedInertia, sourceChild.Inertia, out summedInertia);
- targetChild.LocalPose.Orientation = sourceChild.LocalPose.Orientation;
- targetChild.ShapeIndex = sourceChild.ShapeIndex;
- }
- Symmetric3x3.Invert(summedInertia, out inertia.InverseInertiaTensor);
- }
-
- ///
- /// Builds a buffer of compound children from the accumulated set for a dynamic compound. Does not recenter the children. Does not reset the accumulator.
- ///
- /// List of children created from the accumulated set.
- /// Combined inertia of the compound.
- public void BuildDynamicCompound(out Buffer children, out BodyInertia inertia)
- {
- float totalWeight = 0;
- for (int i = 0; i < Children.Count; ++i)
- {
- totalWeight += Children[i].Weight;
- }
- Debug.Assert(totalWeight > 0, "The compound as a whole must have nonzero weight when creating a dynamic compound.");
-
- inertia.InverseMass = 1f / totalWeight;
- Pool.Take(Children.Count, out children);
- Symmetric3x3 summedInertia = default;
- for (int i = 0; i < Children.Count; ++i)
- {
- ref var sourceChild = ref Children[i];
- ref var targetChild = ref children[i];
- targetChild.LocalPose.Position = sourceChild.LocalPose.Position;
- GetOffsetInertiaContribution(targetChild.LocalPose.Position, sourceChild.Weight, out var contribution);
- Symmetric3x3.Add(contribution, summedInertia, out summedInertia);
- Symmetric3x3.Add(summedInertia, sourceChild.Inertia, out summedInertia);
- targetChild.LocalPose.Orientation = sourceChild.LocalPose.Orientation;
- targetChild.ShapeIndex = sourceChild.ShapeIndex;
- }
- Symmetric3x3.Invert(summedInertia, out inertia.InverseInertiaTensor);
- }
-
- ///
- /// Builds a buffer of compound children from the accumulated set for a kinematic compound.
- /// Computes a center of mass and recenters child shapes relative to it. Does not reset the accumulator.
- ///
- /// List of children created from the accumulated set.
- /// Combined inertia of the compound.
- /// Computed center of rotation based on the poses and weights of accumulated children.
- public void BuildKinematicCompound(out Buffer children, out Vector3 center)
- {
- center = new Vector3();
- float totalWeight = 0;
- for (int i = 0; i < Children.Count; ++i)
- {
- center += Children[i].LocalPose.Position * Children[i].Weight;
- totalWeight += Children[i].Weight;
- }
- Debug.Assert(totalWeight > 0, "The compound as a whole must have nonzero weight when using a recentering build. The center is undefined.");
-
- var inverseWeight = 1f / totalWeight;
- center *= inverseWeight;
- Pool.Take(Children.Count, out children);
- for (int i = 0; i < Children.Count; ++i)
- {
- ref var sourceChild = ref Children[i];
- ref var targetChild = ref children[i];
- targetChild.LocalPose.Position = sourceChild.LocalPose.Position - center;
- targetChild.LocalPose.Orientation = sourceChild.LocalPose.Orientation;
- targetChild.ShapeIndex = sourceChild.ShapeIndex;
- }
- }
-
- ///
- /// Builds a buffer of compound children from the accumulated set for a kinematic compound. Does not recenter children. Does not reset the accumulator.
- ///
- /// List of children created from the accumulated set.
- /// Combined inertia of the compound.
- /// Computed center of rotation based on the poses and weights of accumulated children.
- public void BuildKinematicCompound(out Buffer children)
- {
- Pool.Take(Children.Count, out children);
- for (int i = 0; i < Children.Count; ++i)
- {
- ref var sourceChild = ref Children[i];
- ref var targetChild = ref children[i];
- targetChild.LocalPose.Position = sourceChild.LocalPose.Position;
- targetChild.LocalPose.Orientation = sourceChild.LocalPose.Orientation;
- targetChild.ShapeIndex = sourceChild.ShapeIndex;
- }
- }
-
- ///
- /// Empties out the accumulated children.
- ///
- public void Reset()
- {
- Children.Count = 0;
- }
-
- ///
- /// Returns internal resources to the pool, rendering the builder unusable.
- ///
- public void Dispose()
- {
- Children.Dispose(Pool);
- }
- }
-}
diff --git a/BepuPhysics/Collidables/ConvexHull.cs b/BepuPhysics/Collidables/ConvexHull.cs
index 74178b415..d04829f7b 100644
--- a/BepuPhysics/Collidables/ConvexHull.cs
+++ b/BepuPhysics/Collidables/ConvexHull.cs
@@ -33,7 +33,7 @@ public override string ToString()
}
}
- public struct ConvexHull : IConvexShape
+ public struct ConvexHull : IConvexShape, IDisposableShape
{
///
/// Bundled points of the convex hull.
@@ -60,7 +60,8 @@ public struct ConvexHull : IConvexShape
/// Computed center of the convex hull before the hull was recentered.
public ConvexHull(Span points, BufferPool pool, out Vector3 center)
{
- ConvexHullHelper.CreateShape(points, pool, out center, out this);
+ if (!ConvexHullHelper.CreateShape(points, pool, out center, out this))
+ throw new ArgumentException("Could not create a convex hull from the point set; is it degenerate? Convex hull shapes must have volume.");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -137,7 +138,7 @@ internal readonly void ComputeBounds(in QuaternionWide orientationWide, out Vect
}
}
- public readonly void ComputeBounds(in Quaternion orientation, out Vector3 min, out Vector3 max)
+ public readonly void ComputeBounds(Quaternion orientation, out Vector3 min, out Vector3 max)
{
QuaternionWide.Broadcast(orientation, out var orientationWide);
ComputeBounds(orientationWide, out min, out max);
@@ -183,26 +184,30 @@ public bool GetNextTriangle(out Vector3 a, out Vector3 b, out Vector3 c)
}
}
- ///
- /// Computes the inertia of the convex hull.
- ///
- /// Mass to scale the inertia tensor with.
- /// Inertia of the convex hull.
- public readonly void ComputeInertia(float mass, out BodyInertia inertia)
+ public readonly BodyInertia ComputeInertia(float mass)
{
var triangleSource = new ConvexHullTriangleSource(this);
- MeshInertiaHelper.ComputeClosedInertia(ref triangleSource, mass, out _, out var inertiaTensor);
+ MeshInertiaHelper.ComputeClosedInertia(ref triangleSource, mass, out var volume, out var inertiaTensor);
+ //While it's possible to go through the construction process on a convex hull with no volume, it'll very likely break ray tests which rely on bounding planes, so we don't support it.
+ Debug.Assert(
+ FaceToVertexIndicesStart.Length > 2 &&
+ volume > 0 && !float.IsNaN(inertiaTensor.XX) &&
+ !float.IsNaN(inertiaTensor.YX) && !float.IsNaN(inertiaTensor.YY) && !float.IsNaN(inertiaTensor.ZX) && !float.IsNaN(inertiaTensor.ZY) && !float.IsNaN(inertiaTensor.ZZ),
+ "Convex hull must have volume.");
+ BodyInertia inertia;
inertia.InverseMass = 1f / mass;
Symmetric3x3.Invert(inertiaTensor, out inertia.InverseInertiaTensor);
+ return inertia;
}
- public readonly ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapeBatches)
+ public static ShapeBatch CreateShapeBatch(BufferPool pool, int initialCapacity, Shapes shapeBatches)
{
return new ConvexHullShapeBatch(pool, initialCapacity);
}
- public readonly bool RayTest(in RigidPose pose, in Vector3 origin, in Vector3 direction, out float t, out Vector3 normal)
+ public readonly bool RayTest(in RigidPose pose, Vector3 origin, Vector3 direction, out float t, out Vector3 normal)
{
+ Debug.Assert(FaceToVertexIndicesStart.Length > 2, "Convex hull appears to be degenerate; convex hull must have volume or ray tests will fail.");
Matrix3x3.CreateFromQuaternion(pose.Orientation, out var orientation);
var shapeToRay = origin - pose.Position;
Matrix3x3.TransformTranspose(shapeToRay, orientation, out var localOrigin);
@@ -285,7 +290,7 @@ public void Dispose(BufferPool bufferPool)
/// Type id of convex hull shapes.
///
public const int Id = 5;
- public readonly int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
+ public static int TypeId { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Id; } }
}
public struct ConvexHullWide : IShapeWide
@@ -294,12 +299,12 @@ public struct ConvexHullWide : IShapeWide
//The "wide" variant is simply a collection of convex hull instances.
public Buffer Hulls;
- public int MinimumWideRayCount => int.MaxValue; //'Wide' ray tests just fall through to scalar tests anyway.
+ public static int MinimumWideRayCount => int.MaxValue; //'Wide' ray tests just fall through to scalar tests anyway.
public bool AllowOffsetMemoryAccess => false;
public int InternalAllocationSize => Vector.Count * Unsafe.SizeOf();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Initialize(in RawBuffer memory)
+ public void Initialize(in Buffer memory)
{
Debug.Assert(memory.Length == InternalAllocationSize);
Hulls = memory.As();
@@ -358,7 +363,7 @@ public void GetBounds(ref QuaternionWide orientations, int countInBundle, out Ve
maximumAngularExpansion = maximumRadius;
}
- public void RayTest(ref RigidPoses poses, ref RayWide rayWide, out Vector intersected, out Vector t, out Vector3Wide normal)
+ public void RayTest(ref RigidPoseWide poses, ref RayWide rayWide, out Vector intersected, out Vector t, out Vector3Wide normal)
{
Unsafe.SkipInit(out intersected);
Unsafe.SkipInit(out t);
@@ -366,7 +371,7 @@ public void RayTest(ref RigidPoses poses, ref RayWide rayWide, out Vector i
Debug.Assert(Hulls.Length > 0 && Hulls.Length <= Vector.Count);
for (int i = 0; i < Hulls.Length; ++i)
{
- RigidPoses.ReadFirst(GatherScatter.GetOffsetInstance(ref poses, i), out var pose);
+ RigidPoseWide.ReadFirst(GatherScatter.GetOffsetInstance(ref poses, i), out var pose);
ref var offsetRay = ref GatherScatter.GetOffsetInstance(ref rayWide, i);
Vector3Wide.ReadFirst(offsetRay.Origin, out var origin);
Vector3Wide.ReadFirst(offsetRay.Direction, out var direction);
@@ -406,6 +411,13 @@ public void WriteFirst(in ConvexHull source)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteSlot(int index, in ConvexHull source)
{
+ Debug.Assert(
+ source.Points.Allocated && source.BoundingPlanes.Allocated && source.FaceToVertexIndicesStart.Allocated && source.FaceVertexIndices.Allocated &&
+ (uint)source.Points.Length < 100000 &&
+ (uint)source.BoundingPlanes.Length < 100000 &&
+ (uint)source.FaceToVertexIndicesStart.Length < 100000 &&
+ (uint)source.FaceVertexIndices.Length < 100000,
+ "If a convex hull has an extremely large (or negative) count on any of its buffers, it is very likely undefined trash data.");
Hulls[index] = source;
}
}
diff --git a/BepuPhysics/Collidables/ConvexHullHelper.cs b/BepuPhysics/Collidables/ConvexHullHelper.cs
index 29d54acda..0acc332c7 100644
--- a/BepuPhysics/Collidables/ConvexHullHelper.cs
+++ b/BepuPhysics/Collidables/ConvexHullHelper.cs
@@ -1,955 +1,1238 @@
-using BepuUtilities;
-using BepuUtilities.Collections;
-using BepuUtilities.Memory;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace BepuPhysics.Collidables
-{
- ///
- /// Stores references to the points composing one of a convex hull's faces.
- ///
- public struct HullFace
- {
- public Buffer OriginalVertexMapping;
- public Buffer VertexIndices;
-
- ///
- /// Gets the number of vertices in the face.
- ///
- public int VertexCount
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get { return VertexIndices.Length; }
- }
-
- ///
- /// Gets the index of the vertex associated with the given face vertex index in the source point set.
- ///
- /// Index into the face's vertex list.
- /// Index of the vertex associated with the given face vertex index in the source point set.
- public int this[int index]
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- get { return OriginalVertexMapping[VertexIndices[index]]; }
- }
- }
-
- ///
- /// Raw data representing a convex hull.
- ///
- /// This is not yet transformed into a runtime format. It requires additional processing to be used in a ConvexHull shape; see ConvexHullHelper.ProcessHull.
- public struct HullData
- {
- ///
- /// Mapping of points on the convex hull back to the original point set.
- ///
- public Buffer OriginalVertexMapping;
- ///
- /// List of indices composing the faces of the hull. Individual faces indexed by the FaceIndices.
- ///
- public Buffer FaceVertexIndices;
- ///
- /// Starting index in the FaceVertexIndices for each face.
- ///
- public Buffer FaceStartIndices;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void GetFace(int faceIndex, out HullFace face)
- {
- var nextFaceIndex = faceIndex + 1;
- var start = FaceStartIndices[faceIndex];
- var end = nextFaceIndex == FaceStartIndices.Length ? FaceVertexIndices.Length : FaceStartIndices[nextFaceIndex];
- FaceVertexIndices.Slice(start, end - start, out face.VertexIndices);
- face.OriginalVertexMapping = OriginalVertexMapping;
- }
-
- public void Dispose(BufferPool pool)
- {
- pool.Return(ref OriginalVertexMapping);
- pool.Return(ref FaceVertexIndices);
- pool.Return(ref FaceStartIndices);
- }
- }
-
- ///
- /// Helper methods to create and process convex hulls from point clouds.
- ///
- public static class ConvexHullHelper
- {
- static void FindExtremeFace(in Vector3Wide basisX, in Vector3Wide basisY, in Vector3Wide basisOrigin, in EdgeEndpoints sourceEdgeEndpoints, ref Buffer pointBundles, in Vector indexOffsets, int pointCount,
- ref Buffer> projectedOnX, ref Buffer> projectedOnY, in Vector planeEpsilon, ref QuickList vertexIndices, out Vector3 faceNormal)
- {
- Debug.Assert(projectedOnX.Length >= pointBundles.Length && projectedOnY.Length >= pointBundles.Length && vertexIndices.Count == 0 && vertexIndices.Span.Length >= pointBundles.Length * Vector.Count);
- //Find the candidate-basisOrigin which has the smallest angle with basisY when projected onto the plane spanned by basisX and basisY.
- //angle = atan(y / x)
- //tanAngle = y / x
- //x is guaranteed to be nonnegative, so its sign doesn't change.
- //tanAngle is monotonically increasing with respect to y / x, so a higher angle corresponds to a higher y/x, always.
- //We can then compare samples 0 and 1 using:
- //tanAngle0 > tanAngle1
- //y0 / x0 > y1 / x1
- //y0 * x1 > y1 * x0
- Vector3Wide.Subtract(pointBundles[0], basisOrigin, out var toCandidate);
- ref var x = ref projectedOnX[0];
- ref var y = ref projectedOnY[0];
- Vector3Wide.Dot(basisX, toCandidate, out x);
- //If x is negative, that means some numerical issue has resulted in a point beyond the bounding plane that generated this face request.
- //We'll treat it as if it's on the plane.
- x = Vector.Max(Vector.Zero, x);
- Vector3Wide.Dot(basisY, toCandidate, out y);
- var bestY = y;
- var bestX = x;
- //Ignore the source edge.
- var edgeIndexA = new Vector(sourceEdgeEndpoints.A);
- var edgeIndexB = new Vector(sourceEdgeEndpoints.B);
- var pointCountBundle = new Vector(pointCount);
- //Note that any slot that would have been considered coplanar with the edge triggering this test is ignored by the plane epsilon.
- var ignoreSlot = Vector.BitwiseOr(
- Vector.BitwiseOr(Vector.GreaterThanOrEqual(indexOffsets, pointCountBundle), Vector.LessThan(bestX, planeEpsilon)),
- Vector.BitwiseOr(Vector.Equals(indexOffsets, edgeIndexA), Vector.Equals(indexOffsets, edgeIndexB)));
- bestX = Vector.ConditionalSelect(ignoreSlot, Vector.One, bestX);
- bestY = Vector.ConditionalSelect(ignoreSlot, new Vector(float.MinValue), bestY);
- for (int i = 1; i < pointBundles.Length; ++i)
- {
- Vector3Wide.Subtract(pointBundles[i], basisOrigin, out toCandidate);
- x = ref projectedOnX[i];
- y = ref projectedOnY[i];
- Vector3Wide.Dot(basisX, toCandidate, out x);
- x = Vector.Max(Vector.Zero, x); //Same as earlier- protect against numerical error finding points beyond the bounding plane.
- Vector3Wide.Dot(basisY, toCandidate, out y);
-
- var candidateIndices = indexOffsets + new Vector(i << BundleIndexing.VectorShift);
- ignoreSlot = Vector.BitwiseOr(
- Vector.BitwiseOr(Vector.GreaterThanOrEqual(candidateIndices, pointCountBundle), Vector.LessThan(x, planeEpsilon)),
- Vector.BitwiseOr(Vector.Equals(candidateIndices, edgeIndexA), Vector.Equals(candidateIndices, edgeIndexB)));
- var useCandidate = Vector.AndNot(Vector.GreaterThan(y * bestX, bestY * x), ignoreSlot);
- bestY = Vector.ConditionalSelect(useCandidate, y, bestY);
- bestX = Vector.ConditionalSelect(useCandidate, x, bestX);
- }
- var bestYNarrow = bestY[0];
- var bestXNarrow = bestX[0];
- for (int i = 1; i < Vector.Count; ++i)
- {
- var candidateNumerator = bestY[i];
- var candidateDenominator = bestX[i];
- if (candidateNumerator * bestXNarrow > bestYNarrow * candidateDenominator)
- {
- bestYNarrow = candidateNumerator;
- bestXNarrow = candidateDenominator;
- }
- }
- //We now have the best index, but there may have been multiple vertices on the same plane. Capture all of them at once by doing a second pass over the results.
- //The plane normal we want to examine is (-bestY, bestX) / ||(-bestY, bestX)||.
- //(This isn't wonderfully fast, but it's fairly simple. The alternatives are things like incrementally combining coplanar triangles as they are discovered
- //or using a postpass that looks for coplanar triangles after they've been created.)
- //Rotate the offset to point outward.
- var projectedPlaneNormalNarrow = Vector2.Normalize(new Vector2(-bestYNarrow, bestXNarrow));
- Vector2Wide.Broadcast(projectedPlaneNormalNarrow, out var projectedPlaneNormal);
- for (int i = 0; i < pointBundles.Length; ++i)
- {
- var dot = projectedOnX[i] * projectedPlaneNormal.X + projectedOnY[i] * projectedPlaneNormal.Y;
- var coplanar = Vector.LessThanOrEqual(Vector.Abs(dot), planeEpsilon);
- if (Vector.LessThanAny(coplanar, Vector.Zero))
- {
- var bundleBaseIndex = i << BundleIndexing.VectorShift;
- var localIndexMaximum = pointCount - bundleBaseIndex;
- if (localIndexMaximum > Vector.Count)
- localIndexMaximum = Vector.Count;
- for (int j = 0; j < localIndexMaximum; ++j)
- {
- if (coplanar[j] < 0)
- {
- vertexIndices.AllocateUnsafely() = bundleBaseIndex + j;
- }
- }
- }
- }
- Vector3Wide.ReadFirst(basisX, out var basisXNarrow);
- Vector3Wide.ReadFirst(basisY, out var basisYNarrow);
- faceNormal = basisXNarrow * projectedPlaneNormalNarrow.X + basisYNarrow * projectedPlaneNormalNarrow.Y;
- }
-
-
- static int FindNextIndexForFaceHull(Vector2 start, Vector2 previousEdgeDirection, float planeEpsilon, ref QuickList facePoints)
- {
- //Use a AOS version since the number of points on a given face will tend to be very small in most cases.
- //Same idea as the 3d version- find the next edge which is closest to the previous edge. Not going to worry about collinear points here for now.
- var bestIndex = -1;
- float best = -float.MaxValue;
- float bestDistanceSquared = 0;
- var startToCandidate = facePoints[0] - start;
- var xDirection = new Vector2(previousEdgeDirection.Y, -previousEdgeDirection.X);
- var candidateX = Vector2.Dot(startToCandidate, xDirection);
- var candidateY = Vector2.Dot(startToCandidate, previousEdgeDirection);
- var currentEdgeDirectionX = previousEdgeDirection.X;
- var currentEdgeDirectionY = previousEdgeDirection.Y;
- if (candidateX > 0)
- {
- best = candidateY / candidateX;
- bestIndex = 0;
- bestDistanceSquared = candidateX * candidateX + candidateY * candidateY;
- var inverseBestDistance = 1f / MathF.Sqrt(bestDistanceSquared);
- currentEdgeDirectionX = candidateX * inverseBestDistance;
- currentEdgeDirectionY = candidateY * inverseBestDistance;
- }
- for (int i = 1; i < facePoints.Count; ++i)
- {
- startToCandidate = facePoints[i] - start;
- candidateY = Vector2.Dot(startToCandidate, previousEdgeDirection);
- candidateX = Vector2.Dot(startToCandidate, xDirection);
- //Any points that are collinear *with the previous edge* cannot be a part of the current edge without numerical failure; the previous edge should include them.
- if (candidateX <= 0)
- {
- Debug.Assert(candidateY <= 0,
- "Previous edge should include any collinear points, so this edge should not see any further collinear points beyond its start." +
- "If you run into this, it implies you've found some content that violates the convex huller's assumptions, and I'd appreciate it if you reported it on github.com/bepu/bepuphysics2/issues!" +
- "A .obj or other simple demos-compatible reproduction case would help me fix it.");
- continue;
- }
- //We accept a candidate if it is either:
- //1) collinear with the previous best by the plane epsilon test BUT is more distant, or
- //2) has a greater angle than the previous best.
- var planeOffset = -candidateX * currentEdgeDirectionY + candidateY * currentEdgeDirectionX;
- if (MathF.Abs(planeOffset) < planeEpsilon)
- {
- //The candidate is collinear. Only accept it if it's further away.
- if (candidateX * candidateX + candidateY * candidateY <= bestDistanceSquared)
- {
- continue;
- }
- }
- else if (candidateY < best * candidateX) //candidateY / candidateX < best, given candidate X > 0; just avoiding a division for bulk testing.
- {
- //Candidate is a smaller angle. Rejected.
- continue;
- }
- best = candidateY / candidateX;
- bestDistanceSquared = candidateX * candidateX + candidateY * candidateY;
- var inverseBestDistance = 1f / MathF.Sqrt(bestDistanceSquared);
- currentEdgeDirectionX = candidateX * inverseBestDistance;
- currentEdgeDirectionY = candidateY * inverseBestDistance;
- bestIndex = i;
- }
- //Note that this can return -1 if all points were on top of the start.
- return bestIndex;
- }
-
- static void ReduceFace(ref QuickList faceVertexIndices, in Vector3 faceNormal, Span points, float planeEpsilon, ref QuickList facePoints, ref Buffer allowVertex, ref QuickList reducedIndices)
- {
- Debug.Assert(facePoints.Count == 0 && reducedIndices.Count == 0 && facePoints.Span.Length >= faceVertexIndices.Count && reducedIndices.Span.Length >= faceVertexIndices.Count);
- for (int i = faceVertexIndices.Count - 1; i >= 0; --i)
- {
- if (!allowVertex[faceVertexIndices[i]])
- faceVertexIndices.RemoveAt(i);
- }
- if (faceVertexIndices.Count <= 3)
- {
- //Too small to require computing a hull. Copy directly.
- for (int i = 0; i < faceVertexIndices.Count; ++i)
- {
- reducedIndices.AllocateUnsafely() = faceVertexIndices[i];
- }
- if (faceVertexIndices.Count == 3)
- {
- //No point in running a full reduction, but we do need to check the winding of the triangle.
- ref var a = ref points[reducedIndices[0]];
- ref var b = ref points[reducedIndices[1]];
- ref var c = ref points[reducedIndices[2]];
- //Counterclockwise should result in face normal pointing outward.
- var ab = b - a;
- var ac = c - a;
- var uncalibratedNormal = Vector3.Cross(ab, ac);
- if (uncalibratedNormal.LengthSquared() < 1e-14f)
- {
- //The face is degenerate.
- if (ab.LengthSquared() > 1e-14f)
- {
- allowVertex[reducedIndices[2]] = false;
- reducedIndices.FastRemoveAt(2);
- }
- else if (ac.LengthSquared() > 1e-14f)
- {
- allowVertex[reducedIndices[1]] = false;
- reducedIndices.FastRemoveAt(1);
- }
- else
- {
- allowVertex[reducedIndices[1]] = false;
- allowVertex[reducedIndices[2]] = false;
- reducedIndices.Count = 1;
- }
- }
- else
- {
- if (Vector3.Dot(faceNormal, uncalibratedNormal) < 0)
- Helpers.Swap(ref reducedIndices[0], ref reducedIndices[1]);
- }
- }
- return;
- }
- Helpers.BuildOrthonormalBasis(faceNormal, out var basisX, out var basisY);
- Vector2 centroid = default;
- for (int i = 0; i < faceVertexIndices.Count; ++i)
- {
- ref var source = ref points[faceVertexIndices[i]];
- ref var facePoint = ref facePoints.AllocateUnsafely();
- facePoint = new Vector2(Vector3.Dot(basisX, source), Vector3.Dot(basisY, source));
- centroid += facePoint;
- }
- centroid /= faceVertexIndices.Count;
- var greatestDistanceSquared = -1f;
- var initialIndex = 0;
- for (int i = 0; i < faceVertexIndices.Count; ++i)
- {
- ref var facePoint = ref facePoints[i];
- var distanceSquared = (facePoint - centroid).LengthSquared();
- if (greatestDistanceSquared < distanceSquared)
- {
- greatestDistanceSquared = distanceSquared;
- initialIndex = i;
- }
- }
-
- if (greatestDistanceSquared < 1e-14f)
- {
- //The face is degenerate.
- for (int i = 0; i < faceVertexIndices.Count; ++i)
- {
- allowVertex[faceVertexIndices[i]] = false;
- }
- return;
- }
- var greatestDistance = (float)Math.Sqrt(greatestDistanceSquared);
- var initialOffsetDirection = (facePoints[initialIndex] - centroid) / greatestDistance;
- var previousEdgeDirection = new Vector2(initialOffsetDirection.Y, -initialOffsetDirection.X);
- reducedIndices.AllocateUnsafely() = faceVertexIndices[initialIndex];
-
- var previousEndIndex = initialIndex;
- while (true)
- {
- //This can return -1 in the event of a completely degenerate face.
- var nextIndex = FindNextIndexForFaceHull(facePoints[previousEndIndex], previousEdgeDirection, planeEpsilon, ref facePoints);
- if (nextIndex == -1 || nextIndex == initialIndex)
- {
- break;
- }
- reducedIndices.AllocateUnsafely() = faceVertexIndices[nextIndex];
- previousEdgeDirection = Vector2.Normalize(facePoints[nextIndex] - facePoints[previousEndIndex]);
- previousEndIndex = nextIndex;
- }
-
- //Ignore any vertices which were not on the outer boundary of the face.
- for (int i = 0; i < faceVertexIndices.Count; ++i)
- {
- var index = faceVertexIndices[i];
- if (!reducedIndices.Contains(index))
- {
- allowVertex[index] = false;
- }
- }
- }
-
- [StructLayout(LayoutKind.Explicit)]
- public struct EdgeEndpoints : IEqualityComparerRef
- {
- [FieldOffset(0)]
- public int A;
- [FieldOffset(4)]
- public int B;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public bool Equals(ref EdgeEndpoints a, ref EdgeEndpoints b)
- {
- return Unsafe.As(ref a.A) == Unsafe.As(ref b.A) || (a.A == b.B && a.B == b.A);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int Hash(ref EdgeEndpoints item)
- {
- return item.A ^ item.B;
- }
-
- public override string ToString()
- {
- return $"({A}, {B})";
- }
- }
- struct EdgeToTest
- {
- public EdgeEndpoints Endpoints;
- public Vector3 FaceNormal;
- public int FaceIndex;
- }
-
- //public struct DebugStep
- //{
- // public EdgeEndpoints SourceEdge;
- // public List Raw;
- // public List Reduced;
- // public bool[] AllowVertex;
- // public Vector3 FaceNormal;
- // public Vector3 BasisX;
- // public Vector3 BasisY;
-
- // public DebugStep(EdgeEndpoints sourceEdge, ref QuickList raw, in Vector3 faceNormal, in Vector3 basisX, in Vector3 basisY)
- // {
- // SourceEdge = sourceEdge;
- // FaceNormal = faceNormal;
- // BasisX = basisX;
- // BasisY = basisY;
- // Raw = new List();
- // for (int i = 0; i < raw.Count; ++i)
- // {
- // Raw.Add(raw[i]);
- // }
- // Reduced = default;
- // AllowVertex = default;
- // }
-
- // public void AddReduced(ref QuickList reduced, ref Buffer allowVertex)
- // {
- // Reduced = new List();
- // for (int i = 0; i < reduced.Count; ++i)
- // {
- // Reduced.Add(reduced[i]);
- // }
- // AllowVertex = new bool[allowVertex.Length];
- // for (int i = 0; i < allowVertex.Length; ++i)
- // {
- // AllowVertex[i] = allowVertex[i];
- // }
- // }
-
-
- //}
- /////
- ///// Computes the convex hull of a set of points.
- /////
- ///// Point set to compute the convex hull of.
- ///// Buffer pool to pull memory from when creating the hull.
- ///// Convex hull of the input point set.
- //public static void ComputeHull(Span points, BufferPool pool, out HullData hullData)
- //{
- // ComputeHull(points, pool, out hullData, out _);
- //}
- ///
- /// Computes the convex hull of a set of points.
- ///
- /// Point set to compute the convex hull of.
- /// Buffer pool to pull memory from when creating the hull.
- /// Convex hull of the input point set.
- public static void ComputeHull(Span points, BufferPool pool, out HullData hullData)
- {
- if (points.Length <= 0)
- {
- hullData = default;
- //steps = new List();
- return;
- }
- if (points.Length <= 3)
- {
- //If the input is too small to actually form a volumetric hull, just output the input directly.
- pool.Take(points.Length, out hullData.OriginalVertexMapping);
- for (int i = 0; i < points.Length; ++i)
- {
- hullData.OriginalVertexMapping[i] = i;
- }
- if (points.Length == 3)
- {
- pool.Take(1, out hullData.FaceStartIndices);
- pool.Take(3, out hullData.FaceVertexIndices);
- hullData.FaceStartIndices[0] = 0;
- //No volume, so winding doesn't matter.
- hullData.FaceVertexIndices[0] = 0;
- hullData.FaceVertexIndices[1] = 1;
- hullData.FaceVertexIndices[2] = 2;
- }
- else
- {
- hullData.FaceStartIndices = default;
- hullData.FaceVertexIndices = default;
- }
- //steps = new List();
- return;
- }
- var pointBundleCount = BundleIndexing.GetBundleCount(points.Length);
- pool.Take