Skip to content

Commit

Permalink
Add shapecasts + raycasts (#5440)
Browse files Browse the repository at this point in the history
* Add shapecasts + raycasts

Actual raycasts. Need this for AI LIDAR experiment.

* cassette

* more cudin

* Mostly ported

* more work

* More ports

* the big house

* rays

* builds

* Janky not working raycasts

* Fix GJK

* Test fixes

* Shapecast + fixes

* free

* tests

* More fixes

* Minor changes

* Not these

* Release notes
  • Loading branch information
metalgearsloth authored Dec 16, 2024
1 parent 7982aa2 commit 0b7e8c2
Show file tree
Hide file tree
Showing 19 changed files with 2,220 additions and 181 deletions.
1 change: 1 addition & 0 deletions RELEASE-NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ END TEMPLATE-->

* Added stack-like functions to `ValueList<T>` and added an `AddRange(ReadOnlySpan<T>)` overload.
* Added new `AssetPassFilterDrop`.
* Added a new RayCastSystem with the latest Box2D raycast + shapecasts implemented.

### Bugfixes

Expand Down
15 changes: 15 additions & 0 deletions Robust.Shared.Maths/MathHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,21 @@ public static T CeilMultipleOfPowerOfTwo<T>(T value, T powerOfTwo) where T : IBi
return remainder == T.Zero ? value : (value | mask) + T.One;
}

public static bool IsValid(this float value)
{
if (float.IsNaN(value))
{
return false;
}

if (float.IsInfinity(value))
{
return false;
}

return true;
}

#endregion Public Members
}
}
2 changes: 1 addition & 1 deletion Robust.Shared.Maths/Matrix3Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public static Box2 TransformBox(this Matrix3x2 refFromBox, in Box2 box)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Angle Rotation(this Matrix3x2 t)
{
return new Vector2(t.M11, t.M12).ToAngle();
return new Angle(Math.Atan2(t.M12, t.M11));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
35 changes: 35 additions & 0 deletions Robust.Shared.Maths/Vector2Helpers.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;

namespace Robust.Shared.Maths;

Expand All @@ -14,6 +15,34 @@ public static class Vector2Helpers
/// </summary>
public static readonly Vector2 Half = new(0.5f, 0.5f);

public static bool IsValid(this Vector2 v)
{
if (float.IsNaN(v.X) || float.IsNaN(v.Y))
{
return false;
}

if (float.IsInfinity(v.X) || float.IsInfinity(v.Y))
{
return false;
}

return true;
}

public static Vector2 GetLengthAndNormalize(this Vector2 v, ref float length)
{
length = v.Length();
if (length < float.Epsilon)
{
return Vector2.Zero;
}

float invLength = 1.0f / length;
var n = new Vector2(invLength * v.X, invLength * v.Y);
return n;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2 InterpolateCubic(Vector2 preA, Vector2 a, Vector2 b, Vector2 postB, float t)
{
Expand Down Expand Up @@ -255,6 +284,12 @@ public static Vector2 Cross(float s, in Vector2 a)
return new(-s * a.Y, s * a.X);
}

[Pure]
public static Vector2 RightPerp(this Vector2 v)
{
return new Vector2(v.Y, -v.X);
}

/// <summary>
/// Perform the cross product on a scalar and a vector. In 2D this produces
/// a vector.
Expand Down
212 changes: 212 additions & 0 deletions Robust.Shared/Physics/B2DynamicTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
using System.Numerics;
using System.Runtime.CompilerServices;
using Robust.Shared.Maths;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Utility;

namespace Robust.Shared.Physics
Expand Down Expand Up @@ -943,6 +944,217 @@ public void FastQuery(ref Box2 aabb, FastQueryCallback callback)
private static readonly RayQueryCallback<RayQueryCallback> EasyRayQueryCallback =
(ref RayQueryCallback callback, Proxy proxy, in Vector2 hitPos, float distance) => callback(proxy, hitPos, distance);

internal delegate float RayCallback(RayCastInput input, T context, ref WorldRayCastContext state);

internal void RayCastNew(RayCastInput input, long mask, ref WorldRayCastContext state, RayCallback callback)
{
var p1 = input.Origin;
var d = input.Translation;

var r = d.Normalized();

// v is perpendicular to the segment.
var v = Vector2Helpers.Cross(1.0f, r);
var abs_v = Vector2.Abs(v);

// Separating axis for segment (Gino, p80).
// |dot(v, p1 - c)| > dot(|v|, h)

float maxFraction = input.MaxFraction;

var p2 = Vector2.Add(p1, maxFraction * d);

// Build a bounding box for the segment.
var segmentAABB = new Box2(Vector2.Min(p1, p2), Vector2.Max(p1, p2));

var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
ref var baseRef = ref _nodes[0];
stack.Push(_root);

var subInput = input;

while (stack.GetCount() > 0)
{
var nodeId = stack.Pop();

if (nodeId == Proxy.Free)
{
continue;
}

var node = Unsafe.Add(ref baseRef, nodeId);

if (!node.Aabb.Intersects(segmentAABB))// || ( node->categoryBits & maskBits ) == 0 )
{
continue;
}

// Separating axis for segment (Gino, p80).
// |dot(v, p1 - c)| > dot(|v|, h)
// radius extension is added to the node in this case
var c = node.Aabb.Center;
var h = node.Aabb.Extents;
float term1 = MathF.Abs(Vector2.Dot(v, Vector2.Subtract(p1, c)));
float term2 = Vector2.Dot(abs_v, h);
if ( term2 < term1 )
{
continue;
}

if (node.IsLeaf)
{
subInput.MaxFraction = maxFraction;

float value = callback(subInput, node.UserData, ref state);

if (value == 0.0f)
{
// The client has terminated the ray cast.
return;
}

if (0.0f < value && value < maxFraction)
{
// Update segment bounding box.
maxFraction = value;
p2 = Vector2.Add(p1, maxFraction * d);
segmentAABB.BottomLeft = Vector2.Min( p1, p2 );
segmentAABB.TopRight = Vector2.Max( p1, p2 );
}
}
else
{
var stackCount = stack.GetCount();
Assert( stackCount < 256 - 1 );
if (stackCount < 256 - 1 )
{
// TODO_ERIN just put one node on the stack, continue on a child node
// TODO_ERIN test ordering children by nearest to ray origin
stack.Push(node.Child1);
stack.Push(node.Child2);
}
}
}
}

/// This function receives clipped ray-cast input for a proxy. The function
/// returns the new ray fraction.
/// - return a value of 0 to terminate the ray-cast
/// - return a value less than input->maxFraction to clip the ray
/// - return a value of input->maxFraction to continue the ray cast without clipping
internal delegate float TreeShapeCastCallback(ShapeCastInput input, T userData, ref WorldRayCastContext state);

internal void ShapeCast(ShapeCastInput input, long maskBits, TreeShapeCastCallback callback, ref WorldRayCastContext state)
{
if (input.Count == 0)
{
return;
}

var originAABB = new Box2(input.Points[0], input.Points[0]);

for (var i = 1; i < input.Count; ++i)
{
originAABB.BottomLeft = Vector2.Min(originAABB.BottomLeft, input.Points[i]);
originAABB.TopRight = Vector2.Max(originAABB.TopRight, input.Points[i]);
}

var radius = new Vector2(input.Radius, input.Radius);

originAABB.BottomLeft = Vector2.Subtract(originAABB.BottomLeft, radius);
originAABB.TopRight = Vector2.Add(originAABB.TopRight, radius );

var p1 = originAABB.Center;
var extension = originAABB.Extents;

// v is perpendicular to the segment.
var r = input.Translation;
var v = Vector2Helpers.Cross(1.0f, r);
var abs_v = Vector2.Abs(v);

// Separating axis for segment (Gino, p80).
// |dot(v, p1 - c)| > dot(|v|, h)

float maxFraction = input.MaxFraction;

// Build total box for the shape cast
var t = Vector2.Multiply(maxFraction, input.Translation);

var totalAABB = new Box2(
Vector2.Min(originAABB.BottomLeft, Vector2.Add(originAABB.BottomLeft, t)),
Vector2.Max(originAABB.TopRight, Vector2.Add( originAABB.TopRight, t))
);

var subInput = input;

ref var baseRef = ref _nodes[0];
var stack = new GrowableStack<Proxy>(stackalloc Proxy[256]);
stack.Push(_root);

while (stack.GetCount() > 0)
{
var nodeId = stack.Pop();

if (nodeId == Proxy.Free)
{
continue;
}

var node = Unsafe.Add(ref baseRef, nodeId);
if (!node.Aabb.Intersects(totalAABB))// || ( node->categoryBits & maskBits ) == 0 )
{
continue;
}

// Separating axis for segment (Gino, p80).
// |dot(v, p1 - c)| > dot(|v|, h)
// radius extension is added to the node in this case
var c = node.Aabb.Center;
var h = Vector2.Add(node.Aabb.Extents, extension);
float term1 = MathF.Abs(Vector2.Dot(v, Vector2.Subtract(p1, c)));
float term2 = Vector2.Dot(abs_v, h);
if (term2 < term1)
{
continue;
}

if (node.IsLeaf)
{
subInput.MaxFraction = maxFraction;

float value = callback(subInput, node.UserData, ref state);

if ( value == 0.0f )
{
// The client has terminated the ray cast.
return;
}

if (0.0f < value && value < maxFraction)
{
// Update segment bounding box.
maxFraction = value;
t = Vector2.Multiply(maxFraction, input.Translation);
totalAABB.BottomLeft = Vector2.Min( originAABB.BottomLeft, Vector2.Add(originAABB.BottomLeft, t));
totalAABB.TopRight = Vector2.Max( originAABB.TopRight, Vector2.Add( originAABB.TopRight, t));
}
}
else
{
var stackCount = stack.GetCount();
Assert(stackCount < 256 - 1);

if (stackCount < 255)
{
// TODO_ERIN just put one node on the stack, continue on a child node
// TODO_ERIN test ordering children by nearest to ray origin
stack.Push(node.Child1);
stack.Push(node.Child2);
}
}
}
}

public void RayCast(RayQueryCallback callback, in Ray input)
{
RayCast(ref callback, EasyRayQueryCallback, input);
Expand Down
1 change: 1 addition & 0 deletions Robust.Shared/Physics/BroadPhase/DynamicTreeBroadPhase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ private static Box2 ExtractAabbFunc(in FixtureProxy proxy)
}

public int Count => _tree.NodeCount;
public B2DynamicTree<FixtureProxy> Tree => _tree;

public Box2 GetFatAabb(DynamicTree.Proxy proxy)
{
Expand Down
Loading

0 comments on commit 0b7e8c2

Please sign in to comment.