Skip to content

Commit

Permalink
Merge pull request #202
Browse files Browse the repository at this point in the history
  • Loading branch information
notgiven688 authored Nov 18, 2024
2 parents 2ecb127 + 332e915 commit 4f368eb
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 99 deletions.
2 changes: 1 addition & 1 deletion docs/docs/02_documentation/05-dynamictree.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ In this case the user has to implement a `BroadPhaseFilter` and register it (usi

## Potential pairs

The tree implementation in Jitter needs to be updated using `tree.Update(bool multiThread)`.
The tree implementation in Jitter needs to be updated using `tree.Update(bool multiThread, float dt)`.
This is done automatically for the dynamic tree owned by the world class (`world.DynamicTree`).
This update process generates information about pairs of proxies which either start overlapping, or start to separate.
This 'events' are used to update the `tree.PotentialPairs` hash set, which holds all overlapping pairs.
Expand Down
51 changes: 27 additions & 24 deletions src/Jitter2/Collision/DynamicTree.RayCast.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Jitter2.Collision;
using Jitter2.Collision.Shapes;
using Jitter2.LinearMath;

namespace Jitter2.Collision;
Expand All @@ -40,7 +37,6 @@ public struct RayCastResult
public IDynamicTreeProxy Entity;
public float Lambda;
public JVector Normal;
public bool Hit;
}

/// <summary>
Expand Down Expand Up @@ -94,11 +90,11 @@ public bool RayCast(JVector origin, JVector direction, RayCastFilterPre? pre, Ra
FilterPre = pre,
FilterPost = post
};
var result = QueryRay(ray);
bool hit = QueryRay(ray, out var result);
proxy = result.Entity;
normal = result.Normal;
lambda = result.Lambda;
return result.Hit;
return hit;
}

/// <inheritdoc cref="RayCast(JVector, JVector, RayCastFilterPre?, RayCastFilterPost?, out IDynamicTreeProxy?, out JVector, out float)"/>
Expand All @@ -112,22 +108,28 @@ public bool RayCast(JVector origin, JVector direction, float maxLambda, RayCastF
FilterPost = post,
Lambda = maxLambda
};
var result = QueryRay(ray);
bool hit = QueryRay(ray, out var result);
proxy = result.Entity;
normal = result.Normal;
lambda = result.Lambda;
return result.Hit;
return hit;
}

private RayCastResult QueryRay(in Ray ray)
private bool QueryRay(in Ray ray, out RayCastResult result)
{
if (root == -1) return new RayCastResult();
result = new RayCastResult();

if (root == -1)
{
return false;
}

stack ??= new Stack<int>(256);

stack.Push(root);

RayCastResult result = new();
bool globalHit = false;

result.Lambda = ray.Lambda;

while (stack.Count > 0)
Expand All @@ -143,30 +145,31 @@ private RayCastResult QueryRay(in Ray ray)
if (ray.FilterPre != null && !ray.FilterPre(node.Proxy)) continue;

Unsafe.SkipInit(out RayCastResult res);
res.Hit = irc.RayCast(ray.Origin, ray.Direction, out res.Normal, out res.Lambda);
bool hit = irc.RayCast(ray.Origin, ray.Direction, out res.Normal, out res.Lambda);
res.Entity = node.Proxy;

if (res.Hit && res.Lambda < result.Lambda)
if (hit && res.Lambda < result.Lambda)
{
if (ray.FilterPost != null && !ray.FilterPost(res)) continue;
result = res;
globalHit = true;
}

continue;
}

ref Node lnode = ref Nodes[node.Left];
ref Node rnode = ref Nodes[node.Right];
ref Node lNode = ref Nodes[node.Left];
ref Node rNode = ref Nodes[node.Right];

bool lres = lnode.ExpandedBox.RayIntersect(ray.Origin, ray.Direction, out float enterl);
bool rres = rnode.ExpandedBox.RayIntersect(ray.Origin, ray.Direction, out float enterr);
bool lRes = lNode.ExpandedBox.RayIntersect(ray.Origin, ray.Direction, out float lEnter);
bool rRes = rNode.ExpandedBox.RayIntersect(ray.Origin, ray.Direction, out float rEnter);

if (enterl > result.Lambda) lres = false;
if (enterr > result.Lambda) rres = false;
if (lEnter > result.Lambda) lRes = false;
if (rEnter > result.Lambda) rRes = false;

if (lres && rres)
if (lRes && rRes)
{
if (enterl < enterr)
if (lEnter < rEnter)
{
stack.Push(node.Right);
stack.Push(node.Left);
Expand All @@ -179,11 +182,11 @@ private RayCastResult QueryRay(in Ray ray)
}
else
{
if (lres) stack.Push(node.Left);
if (rres) stack.Push(node.Right);
if (lRes) stack.Push(node.Left);
if (rRes) stack.Push(node.Right);
}
}

return result;
return globalHit;
}
}
74 changes: 70 additions & 4 deletions src/Jitter2/Collision/DynamicTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,44 @@

namespace Jitter2.Collision;

/// <summary>
/// Represents an object for which the bounding box can be updated.
/// </summary>
public interface IUpdatableBoundingBox
{
/// <summary>
/// Updates the bounding box.
/// </summary>
public void UpdateWorldBoundingBox(float dt);
}

/// <summary>
/// Represents an object that can be intersected by a ray.
/// </summary>
public interface IRayCastable
{
/// <summary>
/// Performs a ray cast against the object, checking if a ray originating from a specified point
/// and traveling in a specified direction intersects with the object.
/// </summary>
/// <param name="origin">The starting point of the ray.</param>
/// <param name="direction">
/// The direction of the ray. This vector does not need to be normalized.
/// </param>
/// <param name="normal">
/// When this method returns, contains the surface normal at the point of intersection, if an intersection occurs.
/// </param>
/// <param name="lambda">
/// When this method returns, contains the scalar value representing the distance along the ray's direction vector
/// from the <paramref name="origin"/> to the intersection point. The hit point can be calculated as:
/// <c>origin + lambda * direction</c>.
/// </param>
/// <returns>
/// <c>true</c> if the ray intersects with the object; otherwise, <c>false</c>.
/// </returns>
public bool RayCast(in JVector origin, in JVector direction, out JVector normal, out float lambda);
}

/// <summary>
/// Represents a dynamic Axis Aligned Bounding Box (AABB) tree. A hashset (refer to <see cref="PairHashSet"/>)
/// maintains a record of potential overlapping pairs.
Expand Down Expand Up @@ -87,6 +125,8 @@ public struct Node
private readonly Stack<int> freeNodes = new();
private int nodePointer = -1;
private int root = NullNode;

private Action<Parallel.Batch> updateBoundingBoxes;

/// <summary>
/// Gets the root of the dynamic tree.
Expand All @@ -113,6 +153,8 @@ public DynamicTree(Func<IDynamicTreeProxy, IDynamicTreeProxy, bool> filter)
Proxies = new ReadOnlyActiveList<IDynamicTreeProxy>(proxies);

scanOverlapsPost = batch => { ScanForOverlaps(batch.BatchIndex, true); };

updateBoundingBoxes = UpdateBoundingBoxesCallback;

Filter = filter;
}
Expand All @@ -122,6 +164,7 @@ public enum Timings
ScanOverlapsPre,
UpdateProxies,
ScanOverlapsPost,
UpdateBoundingBoxes,
Last
}

Expand All @@ -138,13 +181,11 @@ public enum Timings
/// Updates all entities that are marked as active in the active list.
/// </summary>
/// <param name="multiThread">A boolean indicating whether to perform a multi-threaded update.</param>
public void Update(bool multiThread)
public void Update(bool multiThread, float dt)
{
long time = Stopwatch.GetTimestamp();
double invFrequency = 1.0d / Stopwatch.Frequency;

CheckBagCount(multiThread);

void SetTime(Timings type)
{
long ctime = Stopwatch.GetTimestamp();
Expand All @@ -153,12 +194,21 @@ void SetTime(Timings type)
time = ctime;
}

this.step_dt = dt;

CheckBagCount(multiThread);

SetTime(Timings.ScanOverlapsPre);

SetTime(Timings.UpdateBoundingBoxes);

if (multiThread)
{
Proxies.ParallelForBatch(256, updateBoundingBoxes);

const int taskThreshold = 24;
int numTasks = Math.Clamp(proxies.ActiveCount / taskThreshold, 1, ThreadPool.Instance.ThreadCount);

Parallel.ForBatch(0, proxies.ActiveCount, numTasks, scanOverlapsPre);

SetTime(Timings.ScanOverlapsPre);
Expand Down Expand Up @@ -186,7 +236,12 @@ void SetTime(Timings type)
}
else
{
scanOverlapsPre(new Parallel.Batch(0, proxies.ActiveCount));
var batch = new Parallel.Batch(0, Proxies.Active);

UpdateBoundingBoxesCallback(batch);
SetTime(Timings.UpdateBoundingBoxes);

scanOverlapsPre(batch);
SetTime(Timings.ScanOverlapsPre);

var sl = lists[0];
Expand All @@ -204,6 +259,17 @@ void SetTime(Timings type)
}
}

private float step_dt;

private void UpdateBoundingBoxesCallback(Parallel.Batch batch)
{
for (int i = batch.Start; i < batch.End; i++)
{
var proxy = Proxies[i];
if (proxy is IUpdatableBoundingBox sh) sh.UpdateWorldBoundingBox(step_dt);
}
}

/// <summary>
/// Updates the state of the specified entity within the dynamic tree structure.
/// </summary>
Expand Down
32 changes: 0 additions & 32 deletions src/Jitter2/Collision/Shapes/Shape.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,38 +28,6 @@

namespace Jitter2.Collision.Shapes;

public interface IUpdatableBoundingBox
{
public void UpdateWorldBoundingBox(float dt);
}

/// <summary>
/// Represents an object that can be intersected by a ray.
/// </summary>
public interface IRayCastable
{
/// <summary>
/// Performs a ray cast against the object, checking if a ray originating from a specified point
/// and traveling in a specified direction intersects with the object.
/// </summary>
/// <param name="origin">The starting point of the ray.</param>
/// <param name="direction">
/// The direction of the ray. This vector does not need to be normalized.
/// </param>
/// <param name="normal">
/// When this method returns, contains the surface normal at the point of intersection, if an intersection occurs.
/// </param>
/// <param name="lambda">
/// When this method returns, contains the scalar value representing the distance along the ray's direction vector
/// from the <paramref name="origin"/> to the intersection point. The hit point can be calculated as:
/// <c>origin + lambda * direction</c>.
/// </param>
/// <returns>
/// <c>true</c> if the ray intersects with the object; otherwise, <c>false</c>.
/// </returns>
public bool RayCast(in JVector origin, in JVector direction, out JVector normal, out float lambda);
}

/// <summary>
/// The main entity of the collision system. Implements <see cref="ISupportMappable"/> for
/// narrow-phase and <see cref="IDynamicTreeProxy"/> for broad-phase collision detection.
Expand Down
Loading

0 comments on commit 4f368eb

Please sign in to comment.