/* * Initially based on Box2D by Erin Catto, license follows; * * Copyright (c) 2009 Erin Catto http://www.box2d.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; using JetBrains.Annotations; using Robust.Shared.Maths; namespace Robust.Shared.Physics { [PublicAPI] public abstract partial class DynamicTree { public const int MinimumCapacity = 16; protected const float AabbMultiplier = 2f; protected readonly float AabbExtendSize; protected readonly Func GrowthFunc; protected DynamicTree(float aabbExtendSize, Func? growthFunc) { AabbExtendSize = aabbExtendSize; GrowthFunc = growthFunc ?? DefaultGrowthFunc; } private static int DefaultGrowthFunc(int x) => x * 2; } [PublicAPI] [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] public sealed class DynamicTree : IBroadPhase where T : notnull { public delegate Box2 ExtractAabbDelegate(in T value); public delegate bool QueryCallbackDelegate(in T value); public delegate bool QueryCallbackDelegate(ref TState state, in T value); public delegate bool RayQueryCallbackDelegate(in T value, in Vector2 point, float distFromOrigin); public delegate bool RayQueryCallbackDelegate(ref TState state, in T value, in Vector2 point, float distFromOrigin); private readonly IEqualityComparer _equalityComparer; private readonly ExtractAabbDelegate _extractAabb; // avoids "Collection was modified; enumeration operation may not execute." private Dictionary _nodeLookup; public readonly B2DynamicTree _b2Tree; public DynamicTree(ExtractAabbDelegate extractAabbFunc, IEqualityComparer? comparer = null, float aabbExtendSize = 1f / 32, int capacity = 256, Func? growthFunc = null) { capacity = Math.Max(DynamicTree.MinimumCapacity, capacity); _extractAabb = extractAabbFunc; _equalityComparer = comparer ?? EqualityComparer.Default; _nodeLookup = new Dictionary(_equalityComparer); _b2Tree = new B2DynamicTree(aabbExtendSize, capacity, growthFunc); } public int Capacity => _b2Tree.Capacity; public int Height => _b2Tree.Height; public int MaxBalance => _b2Tree.MaxBalance; public float AreaRatio => _b2Tree.AreaRatio; public string DebuggerDisplay => $"Count = {Count}, Capacity = {Capacity}, Height = {Height}, NodeCount = {NodeCount}"; public IEnumerator GetEnumerator() { return _nodeLookup.Keys.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public void Clear() { foreach (var proxy in _nodeLookup.Values) { _b2Tree.DestroyProxy(proxy); } } public bool Contains(T item) => _nodeLookup.ContainsKey(item); public void CopyTo(T[] array, int arrayIndex) => _nodeLookup.Keys.CopyTo(array, arrayIndex); public int NodeCount { get; private set; } public int Count => _nodeLookup.Count; public bool IsReadOnly => false; void ICollection.Add(T item) => Add(item); [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Add(in T item, Box2? aabb = null) { if (TryGetProxy(item, out var proxy)) { return false; } aabb ??= _extractAabb(item); if (aabb.Value.HasNan()) { _nodeLookup[item] = DynamicTree.Proxy.Free; return true; } proxy = _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item); _nodeLookup[item] = proxy; return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryGetProxy(in T item, out DynamicTree.Proxy proxy) => _nodeLookup.TryGetValue(item, out proxy); [MethodImpl(MethodImplOptions.AggressiveInlining)] public Box2? GetNodeBounds(T item) => TryGetProxy(item, out var proxy) ? _b2Tree.GetFatAabb(proxy) : null; [MethodImpl(MethodImplOptions.AggressiveInlining)] public Box2? GetNodeBounds(in T item) => TryGetProxy(item, out var proxy) ? _b2Tree.GetFatAabb(proxy) : null; public bool Remove(in T item) { if (!_nodeLookup.Remove(item, out var proxy)) { return false; } if (proxy != DynamicTree.Proxy.Free) { _b2Tree.DestroyProxy(proxy); } return true; } bool ICollection.Remove(T item) => Remove(item); [MethodImpl(MethodImplOptions.NoInlining)] public bool Update(in T item, Box2? newBox = null) { ref var proxy = ref CollectionsMarshal.GetValueRefOrNullRef(_nodeLookup, item); if (Unsafe.IsNullRef(ref proxy)) return false; newBox ??= _extractAabb(item); if (newBox.Value.HasNan()) { if (proxy == DynamicTree.Proxy.Free) return false; _b2Tree.DestroyProxy(proxy); proxy = DynamicTree.Proxy.Free; return true; } if (proxy == DynamicTree.Proxy.Free) { proxy = _b2Tree.CreateProxy(newBox.Value, uint.MaxValue, item); return true; } _b2Tree.MoveProxy(proxy, newBox.Value); return true; } public void QueryAabb(QueryCallbackDelegate callback, Box2 aabb, bool approx = false) { QueryAabb(ref callback, EasyQueryCallback, aabb, approx); } public void QueryAabb(ref TState state, QueryCallbackDelegate callback, Box2 aabb, bool approx = false) { var tuple = (state, _b2Tree, callback, aabb, approx, _extractAabb); _b2Tree.Query(ref tuple, DelegateCache.AabbQueryState, aabb); state = tuple.state; } public IEnumerable QueryAabb(Box2 aabb, bool approx = false) { var list = new List(); QueryAabb(ref list, static (ref List lst, in T i) => { lst.Add(i); return true; }, aabb, approx); return list; } public void QueryPoint(QueryCallbackDelegate callback, Vector2 point, bool approx = false) { QueryPoint(ref callback, EasyQueryCallback, point, approx); } public void QueryPoint(ref TState state, QueryCallbackDelegate callback, Vector2 point, bool approx = false) { var tuple = (state, _b2Tree, callback, point, approx, _extractAabb); _b2Tree.Query(ref tuple, (ref (TState state, B2DynamicTree tree, QueryCallbackDelegate callback, Vector2 point, bool approx, ExtractAabbDelegate extract) tuple, DynamicTree.Proxy proxy) => { var item = tuple.tree.GetUserData(proxy)!; if (!tuple.approx) { var precise = tuple.extract(item); if (!precise.Contains(tuple.point)) { return true; } } return tuple.callback(ref tuple.state, item); }, Box2.CenteredAround(point, new Vector2(0.1f, 0.1f))); state = tuple.state; } public IEnumerable QueryPoint(Vector2 point, bool approx = false) { var list = new List(); QueryPoint(ref list, (ref List list, in T i) => { list.Add(i); return true; }, point, approx); return list; } private static readonly QueryCallbackDelegate EasyQueryCallback = (ref QueryCallbackDelegate s, in T v) => s(v); public void QueryRay(ref TState state, RayQueryCallbackDelegate callback, in Ray ray, bool approx = false) { var tuple = (state, callback, _b2Tree, approx ? null : _extractAabb, ray); _b2Tree.RayCast(ref tuple, DelegateCache.RayQueryState, ray); state = tuple.state; } private static bool AabbQueryStateCallback(ref (TState state, B2DynamicTree tree, QueryCallbackDelegate callback, Box2 aabb, bool approx, ExtractAabbDelegate extract) tuple, DynamicTree.Proxy proxy) { var item = tuple.tree.GetUserData(proxy)!; if (!tuple.approx) { var precise = tuple.extract(item); if (!precise.Intersects(tuple.aabb)) { return true; } } return tuple.callback(ref tuple.state, item); } private static bool RayQueryStateCallback(ref (TState state, RayQueryCallbackDelegate callback, B2DynamicTree tree, ExtractAabbDelegate? extract, Ray srcRay) tuple, DynamicTree.Proxy proxy, in Vector2 hitPos, float distance) { var item = tuple.tree.GetUserData(proxy)!; var hit = hitPos; if (tuple.extract != null) { var precise = tuple.extract(item); if (!tuple.srcRay.Intersects(precise, out distance, out hit)) { return true; } } return tuple.callback(ref tuple.state, item, hit, distance); } public void QueryRay(RayQueryCallbackDelegate callback, in Ray ray, bool approx = false) { QueryRay(ref callback, RayQueryDelegateCallbackInst, ray, approx); } private static readonly RayQueryCallbackDelegate RayQueryDelegateCallbackInst = RayQueryDelegateCallback; private static bool RayQueryDelegateCallback(ref RayQueryCallbackDelegate state, in T value, in Vector2 point, float distFromOrigin) { return state(value, point, distFromOrigin); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddOrUpdate(T item, Box2? aabb = null) { aabb ??= _extractAabb(item); ref var proxy = ref CollectionsMarshal.GetValueRefOrAddDefault(_nodeLookup, item, out var exists); if (!exists) { proxy = aabb.Value.HasNan() ? DynamicTree.Proxy.Free : _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item); return; } if (aabb.Value.HasNan()) { if (proxy == DynamicTree.Proxy.Free) return; _b2Tree.DestroyProxy(proxy); proxy = DynamicTree.Proxy.Free; return; } if (proxy == DynamicTree.Proxy.Free) proxy = _b2Tree.CreateProxy(aabb.Value, uint.MaxValue, item); else _b2Tree.MoveProxy(proxy, aabb.Value); } [Conditional("DEBUG_DYNAMIC_TREE")] [Conditional("DEBUG_DYNAMIC_TREE_ASSERTS")] [DebuggerNonUserCode] [DebuggerHidden] [DebuggerStepThrough] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Assert(bool assertion, [CallerMemberName] string? member = default, [CallerFilePath] string? file = default, [CallerLineNumber] int line = default) { if (assertion) return; var msg = $"Assertion failure in {member} ({file}:{line})"; Debug.Print(msg); Debugger.Break(); throw new InvalidOperationException(msg); } private static class DelegateCache { public static readonly B2DynamicTree.QueryCallback<(TState state, B2DynamicTree tree, QueryCallbackDelegate callback, Box2 aabb, bool approx, ExtractAabbDelegate extract)> AabbQueryState = AabbQueryStateCallback; public static readonly B2DynamicTree.RayQueryCallback<(TState state, RayQueryCallbackDelegate callback, B2DynamicTree tree, ExtractAabbDelegate? extract, Ray srcRay)> RayQueryState = RayQueryStateCallback; } } }