zz
This commit is contained in:
174
GlobalShared/API/GlobalAPI.cs
Normal file
174
GlobalShared/API/GlobalAPI.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.Collections.Generic;
|
||||
using Global.Shared.OcTree;
|
||||
using Global.Shared.OcTree.Data;
|
||||
using Global.Shared.Plugin;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.ModAPI;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.API
|
||||
{
|
||||
public static class GlobalAPI
|
||||
{
|
||||
private static OcTreeHandler OcTreeHandler => GlobalStatic.OcTreeHandler;
|
||||
|
||||
public static bool IsAwareOfGrid(long entityId)
|
||||
{
|
||||
return OcTreeHandler.GridTree.Contains(entityId);
|
||||
}
|
||||
|
||||
public static IGridData GetGridById(long entityId)
|
||||
{
|
||||
IGridData grid = OcTreeHandler.GridTree.Get(entityId);
|
||||
if (grid != null) return grid;
|
||||
var entity = MyAPIGateway.Entities.GetEntityById(entityId);
|
||||
if (!(entity is MyCubeGrid g))
|
||||
{
|
||||
var text = entity != null ? $"a {entity.GetType().Name}" : "non existent";
|
||||
GlobalInstance.Log.Error($"GetGridById: Entity is not a grid, it is a {text}");
|
||||
return null;
|
||||
}
|
||||
|
||||
grid = OcTreeHandler.AddGrid(g);
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
public static IEnumerable<IGridData> GetAllGrids()
|
||||
{
|
||||
return OcTreeHandler.GridTree.GetAll();
|
||||
}
|
||||
|
||||
#region Experimental Spatial Data API
|
||||
|
||||
public static IEnumerable<IGridData> LocateGridsExperimental(BoundingBoxD box)
|
||||
{
|
||||
return OcTreeHandler.GridTree.GetEnumerable(box);
|
||||
}
|
||||
|
||||
public static IEnumerable<IGridData> LocateGridsExperimental(BoundingSphereD sphere)
|
||||
{
|
||||
return OcTreeHandler.GridTree.GetEnumerable(sphere);
|
||||
}
|
||||
|
||||
public static IEnumerable<IGridData> LocateGridsExperimental(MyOrientedBoundingBoxD box)
|
||||
{
|
||||
return OcTreeHandler.GridTree.GetEnumerable(box);
|
||||
}
|
||||
|
||||
public static IEnumerable<IControllableEntityData> LocateCharactersExperimental(BoundingBoxD box)
|
||||
{
|
||||
return OcTreeHandler.CharacterTree.GetEnumerable(box);
|
||||
}
|
||||
|
||||
public static IEnumerable<IControllableEntityData> LocateCharactersExperimental(BoundingSphereD sphere)
|
||||
{
|
||||
return OcTreeHandler.CharacterTree.GetEnumerable(sphere);
|
||||
}
|
||||
|
||||
public static IEnumerable<IControllableEntityData> LocateCharactersExperimental(MyOrientedBoundingBoxD box)
|
||||
{
|
||||
return OcTreeHandler.CharacterTree.GetEnumerable(box);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Spatial Data API
|
||||
|
||||
public static void LocateGrids(List<IGridData> list, BoundingBoxD box, long flags = 0L)
|
||||
{
|
||||
OcTreeHandler.GridTree.GetExact(list, box, flags);
|
||||
}
|
||||
|
||||
public static void LocateGrids(List<IGridData> list, BoundingSphereD sphere, long flags = 0L)
|
||||
{
|
||||
OcTreeHandler.GridTree.GetExact(list, sphere, flags);
|
||||
}
|
||||
|
||||
public static void LocateGrids(List<IGridData> list, MyOrientedBoundingBoxD box, long flags = 0L)
|
||||
{
|
||||
OcTreeHandler.GridTree.GetExact(list, box, flags);
|
||||
}
|
||||
|
||||
public static void LocateCharacters(List<IControllableEntityData> list, BoundingBoxD box, long flags = 0L)
|
||||
{
|
||||
OcTreeHandler.CharacterTree.GetExact(list, box, flags);
|
||||
}
|
||||
|
||||
public static void LocateCharacters(List<IControllableEntityData> list, BoundingSphereD sphere,
|
||||
long flags = 0L)
|
||||
{
|
||||
OcTreeHandler.CharacterTree.GetExact(list, sphere, flags);
|
||||
}
|
||||
|
||||
public static void LocateCharacters(List<IControllableEntityData> list, MyOrientedBoundingBoxD box,
|
||||
long flags = 0L)
|
||||
{
|
||||
OcTreeHandler.CharacterTree.GetExact(list, box, flags);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Extensions
|
||||
|
||||
private static readonly List<GridData> _internalGridList = new List<GridData>();
|
||||
|
||||
private static readonly List<ControllableEntityData>
|
||||
_internalCharacterList = new List<ControllableEntityData>();
|
||||
|
||||
private static void GetExact(this GenericOcTree<GridData> ocTree, List<IGridData> list, BoundingSphereD sphere,
|
||||
long flags = 0L)
|
||||
{
|
||||
_internalGridList.Clear();
|
||||
ocTree.GetExact(_internalGridList, sphere, flags);
|
||||
list.AddRange(_internalGridList);
|
||||
}
|
||||
|
||||
private static void GetExact(this GenericOcTree<GridData> ocTree, List<IGridData> list, BoundingBoxD box,
|
||||
long flags = 0L)
|
||||
{
|
||||
_internalGridList.Clear();
|
||||
ocTree.GetExact(_internalGridList, box, flags);
|
||||
list.AddRange(_internalGridList);
|
||||
}
|
||||
|
||||
private static void GetExact(this GenericOcTree<GridData> ocTree, List<IGridData> list,
|
||||
MyOrientedBoundingBoxD box,
|
||||
long flags = 0L)
|
||||
{
|
||||
_internalGridList.Clear();
|
||||
ocTree.GetExact(_internalGridList, box, flags);
|
||||
list.AddRange(_internalGridList);
|
||||
}
|
||||
|
||||
private static void GetExact(this GenericOcTree<ControllableEntityData> ocTree,
|
||||
List<IControllableEntityData> list, BoundingSphereD sphere,
|
||||
long flags = 0L)
|
||||
{
|
||||
_internalCharacterList.Clear();
|
||||
ocTree.GetExact(_internalCharacterList, sphere, flags);
|
||||
list.AddRange(_internalCharacterList);
|
||||
}
|
||||
|
||||
private static void GetExact(this GenericOcTree<ControllableEntityData> ocTree,
|
||||
List<IControllableEntityData> list, BoundingBoxD box,
|
||||
long flags = 0L)
|
||||
{
|
||||
_internalCharacterList.Clear();
|
||||
ocTree.GetExact(_internalCharacterList, box, flags);
|
||||
list.AddRange(_internalCharacterList);
|
||||
}
|
||||
|
||||
private static void GetExact(this GenericOcTree<ControllableEntityData> ocTree,
|
||||
List<IControllableEntityData> list,
|
||||
MyOrientedBoundingBoxD box,
|
||||
long flags = 0L)
|
||||
{
|
||||
_internalCharacterList.Clear();
|
||||
ocTree.GetExact(_internalCharacterList, box, flags);
|
||||
list.AddRange(_internalCharacterList);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
17
GlobalShared/API/IBooleanFunction.cs
Normal file
17
GlobalShared/API/IBooleanFunction.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Global.Shared.API
|
||||
{
|
||||
public static class BooleanFunctions
|
||||
{
|
||||
public delegate bool BooleanFunction(bool a, bool b);
|
||||
|
||||
public static BooleanFunction True = (a, b) => true;
|
||||
public static BooleanFunction False = (a, b) => false;
|
||||
public static BooleanFunction And = (a, b) => a && b;
|
||||
public static BooleanFunction Or = (a, b) => a || b;
|
||||
public static BooleanFunction Xor = (a, b) => a ^ b;
|
||||
public static BooleanFunction NotA = (a, b) => b && !a;
|
||||
public static BooleanFunction NotB = (a, b) => a && !b;
|
||||
public static BooleanFunction NotAAndB = (a, b) => !(a && b);
|
||||
public static BooleanFunction NotAOrB = (a, b) => !(a || b);
|
||||
}
|
||||
}
|
9
GlobalShared/API/IControllableEntityData.cs
Normal file
9
GlobalShared/API/IControllableEntityData.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Sandbox.Game.Entities;
|
||||
|
||||
namespace Global.Shared.API
|
||||
{
|
||||
public interface IControllableEntityData
|
||||
{
|
||||
IMyControllableEntity Entity { get; }
|
||||
}
|
||||
}
|
9
GlobalShared/API/ICustomData.cs
Normal file
9
GlobalShared/API/ICustomData.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Global.Shared.API
|
||||
{
|
||||
public interface ICustomData : IDisposable
|
||||
{
|
||||
IGridData GridData { get; }
|
||||
}
|
||||
}
|
7
GlobalShared/API/ICustomManager.cs
Normal file
7
GlobalShared/API/ICustomManager.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Global.Shared.API
|
||||
{
|
||||
public interface ICustomManager<out T> where T : ICustomData
|
||||
{
|
||||
T CreateData(IGridData data);
|
||||
}
|
||||
}
|
26
GlobalShared/API/IGridData.cs
Normal file
26
GlobalShared/API/IGridData.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using Global.Shared.OcTree.Data;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.Game.Entities.Cube;
|
||||
using VRage.ObjectBuilders;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.API
|
||||
{
|
||||
public interface IGridData : IOcTreeData
|
||||
{
|
||||
long Id { get; }
|
||||
BoundingBoxD BoundingBox { get; }
|
||||
MyCubeGrid CubeGrid { get; }
|
||||
|
||||
bool IsInVoxel { get; }
|
||||
|
||||
int BlockCount { get; }
|
||||
|
||||
IEnumerable<MyCubeBlock> GetBlocks(MyObjectBuilderType type);
|
||||
|
||||
IEnumerable<MyFunctionalBlock> GetAllFunctionalBlocks();
|
||||
|
||||
bool Contains(MyObjectBuilderType type);
|
||||
}
|
||||
}
|
34
GlobalShared/API/OcTreeVariables.cs
Normal file
34
GlobalShared/API/OcTreeVariables.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
|
||||
namespace Global.Shared.API
|
||||
{
|
||||
public static class GridFlag
|
||||
{
|
||||
public const long None = 0;
|
||||
private static int _taken;
|
||||
public static long StaticGrid = CreateFlag();
|
||||
public static long DynamicGrid = CreateFlag();
|
||||
public static long SmallGrid = CreateFlag();
|
||||
public static long LargeGrid = CreateFlag();
|
||||
|
||||
public static long CreateFlag()
|
||||
{
|
||||
if (_taken == 31) throw new Exception("Too many grid flags created. Max is 30.");
|
||||
return 1 << _taken++;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlayerFlag
|
||||
{
|
||||
public const long None = 0;
|
||||
private static int _taken;
|
||||
public static long Character = CreateFlag();
|
||||
public static long Controlled = CreateFlag();
|
||||
|
||||
public static long CreateFlag()
|
||||
{
|
||||
if (_taken == 31) throw new Exception("Too many player flags created. Max is 30.");
|
||||
return 1 << _taken++;
|
||||
}
|
||||
}
|
||||
}
|
14
GlobalShared/Config/IPluginConfig.cs
Normal file
14
GlobalShared/Config/IPluginConfig.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.ComponentModel;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.Config
|
||||
{
|
||||
public interface IPluginConfig : INotifyPropertyChanged
|
||||
{
|
||||
bool IsEnabled { get; set; }
|
||||
int Size { get; set; }
|
||||
Vector3I CenterPosition { get; set; }
|
||||
int Capacity { get; set; }
|
||||
int MaxDepth { get; set; }
|
||||
}
|
||||
}
|
27
GlobalShared/Events/BlockEvents.cs
Normal file
27
GlobalShared/Events/BlockEvents.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Sandbox.Definitions;
|
||||
using Sandbox.Game.Entities;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.Events
|
||||
{
|
||||
public class BlockEvents
|
||||
{
|
||||
public delegate void CanPlaceBlockDelegate(MyCubeGrid grid, MyBlockOrientation? orientation,
|
||||
MyCubeBlockDefinition blockDefinition, ulong placingPlayer, ref bool canPlace);
|
||||
|
||||
public static Action<IMyControllableEntity> OnPlayerControlAcquired;
|
||||
public static Action<IMyControllableEntity> OnPlayerControlReleased;
|
||||
|
||||
public static Action<MyCubeBlock> OnBlockAdded;
|
||||
public static Action<MyCubeBlock> OnBlockRemoved;
|
||||
|
||||
public static event CanPlaceBlockDelegate CanPlaceBlockEvent;
|
||||
|
||||
internal static void OnCanPlaceBlockEvent(MyCubeGrid grid, MyBlockOrientation? orientation,
|
||||
MyCubeBlockDefinition def, ulong placingPlayer, ref bool canPlace)
|
||||
{
|
||||
CanPlaceBlockEvent?.Invoke(grid, orientation, def, placingPlayer, ref canPlace);
|
||||
}
|
||||
}
|
||||
}
|
13
GlobalShared/Events/GridEvents.cs
Normal file
13
GlobalShared/Events/GridEvents.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using Sandbox.Game.Entities;
|
||||
|
||||
namespace Global.Shared.Events
|
||||
{
|
||||
public class GridEvents
|
||||
{
|
||||
public static Action<MyCubeGrid> GridCreated = l => { };
|
||||
public static Action<MyCubeGrid> GridRemoved = l => { };
|
||||
public static Action<MyCubeGrid, MyCubeGrid> SubGridAdded = (parent, child) => { };
|
||||
public static Action<MyCubeGrid, MyCubeGrid> SubGridRemoved = (parent, child) => { };
|
||||
}
|
||||
}
|
49
GlobalShared/GlobalShared.projitems
Normal file
49
GlobalShared/GlobalShared.projitems
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
|
||||
<HasSharedItems>true</HasSharedItems>
|
||||
<SharedGUID>B127E405-3C0F-48C3-9F8E-7437E8F4E106</SharedGUID>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Configuration">
|
||||
<Import_RootNamespace>Global.Shared</Import_RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)API\GlobalAPI.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)API\IBooleanFunction.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)API\IControllableEntityData.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)API\ICustomData.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)API\ICustomManager.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)API\IGridData.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)API\OcTreeVariables.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Config\IPluginConfig.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Events\BlockEvents.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Events\GridEvents.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Logging\IPluginLogger.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Logging\LogFormatter.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Logging\LoggingLevel.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Bag.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Data\ControllableEntityData.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Data\GridData.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Data\IOcTreeData.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)OcTree\GenericOcTree.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)OcTree\IContainer.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)OcTree\OcTree.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)OcTree\OcTreeHandler.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)OcTree\Pool.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Patches\CubeGridPatch.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Patches\EntityNamePatch.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Patches\MyConveyorLinePatch.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Patches\MyGamePruningStructurePatch.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Patches\MyMechanicalConnectionBlockBasePatch.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Patches\MyTerminalBlockPatch.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Patches\TerminalSystemPatch.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Patching\PatchHelpers.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Plugin\GlobalInstance.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Plugin\GlobalStatic.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Plugin\IGlobalPlugin.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Util\KeenExtensions.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Util\NexusUtils.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Util\UintCache.cs" />
|
||||
</ItemGroup>
|
||||
</Project>
|
13
GlobalShared/GlobalShared.shproj
Normal file
13
GlobalShared/GlobalShared.shproj
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{C5784FE0-CF0A-4870-9DEF-7BEA8B64C01A}</ProjectGuid>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<RootNamespace>Shared</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
|
||||
<Import Project="GlobalShared.projitems" Label="Shared" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
|
||||
</Project>
|
28
GlobalShared/Logging/IPluginLogger.cs
Normal file
28
GlobalShared/Logging/IPluginLogger.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
|
||||
namespace Global.Shared.Logging
|
||||
{
|
||||
public interface IPluginLogger
|
||||
{
|
||||
bool IsTraceEnabled { get; }
|
||||
bool IsDebugEnabled { get; }
|
||||
bool IsInfoEnabled { get; }
|
||||
bool IsWarningEnabled { get; }
|
||||
bool IsErrorEnabled { get; }
|
||||
bool IsCriticalEnabled { get; }
|
||||
|
||||
void Trace(Exception ex, string message, params object[] data);
|
||||
void Debug(Exception ex, string message, params object[] data);
|
||||
void Info(Exception ex, string message, params object[] data);
|
||||
void Warning(Exception ex, string message, params object[] data);
|
||||
void Error(Exception ex, string message, params object[] data);
|
||||
void Critical(Exception ex, string message, params object[] data);
|
||||
|
||||
void Trace(string message, params object[] data);
|
||||
void Debug(string message, params object[] data);
|
||||
void Info(string message, params object[] data);
|
||||
void Warning(string message, params object[] data);
|
||||
void Error(string message, params object[] data);
|
||||
void Critical(string message, params object[] data);
|
||||
}
|
||||
}
|
90
GlobalShared/Logging/LogFormatter.cs
Normal file
90
GlobalShared/Logging/LogFormatter.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Global.Shared.Logging
|
||||
{
|
||||
public class LogFormatter
|
||||
{
|
||||
private const int MaxExceptionDepth = 100;
|
||||
private readonly string _prefix;
|
||||
private readonly ThreadLocal<StringBuilder> _stringBuilder = new ThreadLocal<StringBuilder>();
|
||||
|
||||
public LogFormatter(string prefix)
|
||||
{
|
||||
_prefix = prefix;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected string Format(Exception ex, string message, object[] data)
|
||||
{
|
||||
// Allocate a single StringBuilder object per thread
|
||||
var sb = _stringBuilder.Value;
|
||||
if (sb == null)
|
||||
{
|
||||
sb = new StringBuilder();
|
||||
_stringBuilder.Value = sb;
|
||||
}
|
||||
|
||||
if (message == null)
|
||||
message = "";
|
||||
|
||||
sb.Append(_prefix);
|
||||
|
||||
sb.Append(data == null || data.Length == 0 ? message : string.Format(message, data));
|
||||
|
||||
FormatException(sb, ex);
|
||||
|
||||
var text = sb.ToString();
|
||||
sb.Clear();
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void FormatException(StringBuilder sb, Exception ex)
|
||||
{
|
||||
if (ex == null)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < MaxExceptionDepth; i++)
|
||||
{
|
||||
sb.Append("\r\n[");
|
||||
sb.Append(ex.GetType().Name);
|
||||
sb.Append("] ");
|
||||
sb.Append(ex.Message);
|
||||
|
||||
if (ex.TargetSite != null)
|
||||
{
|
||||
sb.Append("\r\nMethod: ");
|
||||
sb.Append(ex.TargetSite);
|
||||
}
|
||||
|
||||
if (ex.Data.Count > 0)
|
||||
{
|
||||
sb.Append("\r\nData:");
|
||||
foreach (var key in ex.Data.Keys)
|
||||
{
|
||||
sb.Append("\r\n");
|
||||
sb.Append(key);
|
||||
sb.Append(" = ");
|
||||
sb.Append(ex.Data[key]);
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append("\r\nTraceback:\r\n");
|
||||
sb.Append(ex.StackTrace);
|
||||
|
||||
ex = ex.InnerException;
|
||||
if (ex == null)
|
||||
return;
|
||||
|
||||
sb.Append("\r\nInner exception:\r\n");
|
||||
}
|
||||
|
||||
sb.Append($"WARNING: Not logging more than {MaxExceptionDepth} inner exceptions");
|
||||
}
|
||||
}
|
||||
}
|
20
GlobalShared/Logging/LoggingLevel.cs
Normal file
20
GlobalShared/Logging/LoggingLevel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Global.Shared.Logging
|
||||
{
|
||||
public enum LoggingLevel
|
||||
{
|
||||
Trace,
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Fatal
|
||||
}
|
||||
|
||||
public static class LevelExtensions
|
||||
{
|
||||
public static bool IsOfLevel(this LoggingLevel level, LoggingLevel levelToCheck)
|
||||
{
|
||||
return (int)level <= (int)levelToCheck;
|
||||
}
|
||||
}
|
||||
}
|
173
GlobalShared/OcTree/Bag.cs
Normal file
173
GlobalShared/OcTree/Bag.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Global.Shared.OcTree
|
||||
{
|
||||
public interface IImmutableBag<E> : IEnumerable<E>
|
||||
{
|
||||
/// <summary>Returns the element at the specified position in Bag</summary>
|
||||
/// <param name="index">the index of the element to return</param>
|
||||
/// <returns>the element at the specified position in bag</returns>
|
||||
E Get(int index);
|
||||
|
||||
/// <summary>Returns the number of elements in this Bag</summary>
|
||||
/// <returns>the number of elements in this bag</returns>
|
||||
int Size();
|
||||
|
||||
bool IsEmpty();
|
||||
|
||||
bool Contains(E element);
|
||||
}
|
||||
|
||||
public class Bag<E> : IImmutableBag<E>
|
||||
{
|
||||
public E[] data;
|
||||
private BagEnumerator it;
|
||||
protected int size;
|
||||
|
||||
public Bag() : this(64)
|
||||
{
|
||||
}
|
||||
|
||||
public Bag(int capacity)
|
||||
{
|
||||
data = new E[capacity];
|
||||
}
|
||||
|
||||
public E this[int i]
|
||||
{
|
||||
get => Get(i);
|
||||
set => data[i] = value;
|
||||
}
|
||||
|
||||
public E Get(int index)
|
||||
{
|
||||
return data[index];
|
||||
}
|
||||
|
||||
/// <summary>Returns the number of elements in this Bag</summary>
|
||||
/// <returns>the number of elements in this bag</returns>
|
||||
public int Size()
|
||||
{
|
||||
return size;
|
||||
}
|
||||
|
||||
public bool IsEmpty()
|
||||
{
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
public bool Contains(E e)
|
||||
{
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
var e2 = data[i];
|
||||
|
||||
if (!e.Equals(e2)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public E Remove(int index)
|
||||
{
|
||||
var e = data[index];
|
||||
data[index] = data[--size];
|
||||
data[size] = default;
|
||||
return e;
|
||||
}
|
||||
|
||||
public E RemoveLast()
|
||||
{
|
||||
if (IsEmpty()) return default;
|
||||
|
||||
var e = data[--size];
|
||||
data[size] = default;
|
||||
return e;
|
||||
}
|
||||
|
||||
public bool Remove(E e)
|
||||
{
|
||||
for (var i = 0; i < size; i++)
|
||||
{
|
||||
var e2 = data[i];
|
||||
|
||||
if (!e.Equals(e2)) continue;
|
||||
|
||||
data[i] = data[--size];
|
||||
data[size] = default;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Returns the number of elements this bag can hold without growing.</summary>
|
||||
/// <returns>the number of elements this bag can hold without growing</returns>
|
||||
public int Capacity()
|
||||
{
|
||||
return data.Length;
|
||||
}
|
||||
|
||||
public void Add(E e)
|
||||
{
|
||||
if (size == data.Length) Grow(data.Length + 1);
|
||||
|
||||
data[size++] = e;
|
||||
}
|
||||
|
||||
public void Grow(int newCapacity)
|
||||
{
|
||||
Array.Resize(ref data, newCapacity);
|
||||
}
|
||||
|
||||
#region Enumerator
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerator<E> GetEnumerator()
|
||||
{
|
||||
if (it == null) it = new BagEnumerator(this);
|
||||
|
||||
it.Cursor = -1;
|
||||
|
||||
return it;
|
||||
}
|
||||
|
||||
public class BagEnumerator : IEnumerator<E>
|
||||
{
|
||||
private readonly Bag<E> bag;
|
||||
internal int Cursor;
|
||||
|
||||
public BagEnumerator(Bag<E> bag)
|
||||
{
|
||||
this.bag = bag;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
Cursor++;
|
||||
return Cursor < bag.size;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Cursor = -1;
|
||||
}
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public E Current => bag.data[Cursor];
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
51
GlobalShared/OcTree/Data/ControllableEntityData.cs
Normal file
51
GlobalShared/OcTree/Data/ControllableEntityData.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using Global.Shared.API;
|
||||
using Global.Shared.Plugin;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.Game.Entities.Character;
|
||||
using VRage.Game.Components;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.OcTree.Data
|
||||
{
|
||||
public class ControllableEntityData : IControllableEntityData, IOcTreeData
|
||||
{
|
||||
public ControllableEntityData(IMyControllableEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
Id = entity.Entity.EntityId;
|
||||
Recompute();
|
||||
entity.Entity.PositionComp.OnPositionChanged += PositionChanged;
|
||||
}
|
||||
|
||||
public IMyControllableEntity Entity { get; set; }
|
||||
public long Id { get; }
|
||||
public List<long> ChildIds { get; } = new List<long>();
|
||||
public long Flags { get; private set; }
|
||||
public BoundingBoxD BoundingBox { get; private set; }
|
||||
|
||||
public void Recompute()
|
||||
{
|
||||
Flags = PlayerFlag.None;
|
||||
Flags |= Entity is MyCharacter ? PlayerFlag.Character : PlayerFlag.Controlled;
|
||||
BoundingBox = Entity.Entity.PositionComp.WorldAABB;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Recompute();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Entity.Entity.PositionComp.OnPositionChanged -= PositionChanged;
|
||||
Entity = null;
|
||||
BoundingBox = default;
|
||||
}
|
||||
|
||||
private void PositionChanged(MyPositionComponentBase obj)
|
||||
{
|
||||
GlobalStatic.OcTreeHandler.CharacterTree.Update(Id);
|
||||
}
|
||||
}
|
||||
}
|
334
GlobalShared/OcTree/Data/GridData.cs
Normal file
334
GlobalShared/OcTree/Data/GridData.cs
Normal file
@@ -0,0 +1,334 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Global.Shared.API;
|
||||
using Global.Shared.Events;
|
||||
using Global.Shared.Plugin;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.Game.Entities.Cube;
|
||||
using Sandbox.Game.World;
|
||||
using VRage.Collections;
|
||||
using VRage.Game;
|
||||
using VRage.Game.ModAPI;
|
||||
using VRage.Game.ObjectBuilders.Definitions.SessionComponents;
|
||||
using VRage.ObjectBuilders;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.OcTree.Data
|
||||
{
|
||||
public class GridData : IOcTreeData, IGridData
|
||||
{
|
||||
private static readonly Stopwatch _stopwatch = new Stopwatch();
|
||||
public ConcurrentDictionary<MyObjectBuilderType, ArrayList> Blocks;
|
||||
public MyConcurrentList<MyFunctionalBlock> FunctionalBlocks;
|
||||
public List<MyCubeGrid> SubGrids;
|
||||
|
||||
public GridData(MyCubeGrid grid)
|
||||
{
|
||||
CubeGrid = grid;
|
||||
Id = grid.EntityId;
|
||||
Init();
|
||||
}
|
||||
|
||||
public bool IsInVoxel { get; set; }
|
||||
public int BlockCount { get; set; }
|
||||
|
||||
public MyCubeGrid CubeGrid { get; }
|
||||
|
||||
public IEnumerable<MyCubeBlock> GetBlocks(MyObjectBuilderType type)
|
||||
{
|
||||
// TODO: Improve memory allocation of this.
|
||||
return Blocks.TryGetValue(type, out var list)
|
||||
? list.Cast<MyCubeBlock>().ToArray()
|
||||
: Enumerable.Empty<MyCubeBlock>();
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<MyFunctionalBlock> GetAllFunctionalBlocks()
|
||||
{
|
||||
return FunctionalBlocks ?? Enumerable.Empty<MyFunctionalBlock>();
|
||||
}
|
||||
|
||||
public bool Contains(MyObjectBuilderType type)
|
||||
{
|
||||
return Blocks.ContainsKey(type) && Blocks[type].Count > 0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Recompute();
|
||||
}
|
||||
|
||||
public long Id { get; set; }
|
||||
public List<long> ChildIds => SubGrids?.Select(x => x.EntityId).ToList();
|
||||
public long Flags { get; private set; }
|
||||
public BoundingBoxD BoundingBox { get; private set; }
|
||||
|
||||
public void Recompute()
|
||||
{
|
||||
Flags = GridFlag.None;
|
||||
BoundingBox = CubeGrid.GetPhysicalGroupAABB();
|
||||
Flags |= CubeGrid.IsStatic ? GridFlag.StaticGrid : GridFlag.DynamicGrid;
|
||||
Flags |= CubeGrid.GridSizeEnum == MyCubeSize.Large ? GridFlag.LargeGrid : GridFlag.SmallGrid;
|
||||
BlockCount = CubeGrid.BlocksCount + (SubGrids?.Sum(x => x.BlocksCount) ?? 0);
|
||||
}
|
||||
|
||||
private bool Closing;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GlobalInstance.Log.Debug($"Disposing of grid data for {Id} {CubeGrid.DisplayName}");
|
||||
if (GlobalInstance.Log.IsTraceEnabled) GlobalInstance.Log.Trace(new StackTrace(true).ToString());
|
||||
CubeGrid.OnStaticChanged -= OnStaticChanged;
|
||||
CubeGrid.OnConnectionChanged -= OnConnectionChanged;
|
||||
CubeGrid.OnGridMerge -= OnGridMerged;
|
||||
CubeGrid.OnGridSplit -= OnGridSplit;
|
||||
CubeGrid.OnFatBlockAdded -= AddBlock;
|
||||
CubeGrid.OnFatBlockRemoved -= OnFatBlockRemoved;
|
||||
SubGrids?.ForEach(x =>
|
||||
{
|
||||
x.OnStaticChanged -= OnStaticChanged;
|
||||
x.OnConnectionChanged -= OnConnectionChanged;
|
||||
x.OnGridMerge -= OnGridMerged;
|
||||
x.OnGridSplit -= OnGridSplit;
|
||||
x.OnFatBlockAdded -= AddBlock;
|
||||
x.OnFatBlockRemoved -= OnFatBlockRemoved;
|
||||
});
|
||||
Closing = true;
|
||||
GlobalInstance.Run(() =>
|
||||
{
|
||||
Blocks.Clear();
|
||||
|
||||
FunctionalBlocks.Clear();
|
||||
SubGrids.Clear();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnStaticChanged(MyCubeGrid arg1, bool arg2)
|
||||
{
|
||||
RecheckVoxelStatus();
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
Blocks = new ConcurrentDictionary<MyObjectBuilderType, ArrayList>();
|
||||
FunctionalBlocks = new MyConcurrentList<MyFunctionalBlock>();
|
||||
RecalculateSubgrids();
|
||||
Recompute();
|
||||
CubeGrid.OnStaticChanged += OnStaticChanged;
|
||||
CubeGrid.OnConnectionChanged += OnConnectionChanged;
|
||||
CubeGrid.OnGridMerge += OnGridMerged;
|
||||
CubeGrid.OnGridSplit += OnGridSplit;
|
||||
CubeGrid.OnFatBlockAdded += AddBlock;
|
||||
CubeGrid.OnFatBlockRemoved += OnFatBlockRemoved;
|
||||
|
||||
if (GlobalInstance.Log.IsDebugEnabled)
|
||||
{
|
||||
_stopwatch.Reset();
|
||||
_stopwatch.Start();
|
||||
}
|
||||
|
||||
ScanGridBlocks(CubeGrid);
|
||||
if (SubGrids.Count > 0)
|
||||
SubGrids.ForEach(ScanGridBlocks);
|
||||
RecheckVoxelStatus();
|
||||
|
||||
if (GlobalInstance.Log.IsDebugEnabled)
|
||||
GlobalInstance.Log.Debug(
|
||||
$"Data Init took {_stopwatch.ElapsedMilliseconds}ms for {CubeGrid.DisplayName} with {CubeGrid.BlocksCount + SubGrids.Sum(e => e.BlocksCount)} blocks and {SubGrids.Count} subgrids");
|
||||
}
|
||||
|
||||
private void OnFatBlockRemoved(MyCubeBlock block)
|
||||
{
|
||||
if (Closing) return;
|
||||
if (Blocks.TryGetValue(block.BlockDefinition.Id.TypeId, out var bag)) bag.Remove(block);
|
||||
|
||||
GlobalInstance.Run(() =>
|
||||
{
|
||||
if (block is IMyControllableEntity entity)
|
||||
{
|
||||
entity.ControllerInfo.ControlAcquired -= OnControlAcquired;
|
||||
entity.ControllerInfo.ControlReleased -= OnControlReleased;
|
||||
OnControlReleased(entity.ControllerInfo.Controller);
|
||||
}
|
||||
|
||||
BlockEvents.OnBlockRemoved?.Invoke(block);
|
||||
});
|
||||
}
|
||||
|
||||
public void RecheckVoxelStatus()
|
||||
{
|
||||
if (Closing) return;
|
||||
var settings = new MyGridPlacementSettings
|
||||
{
|
||||
VoxelPlacement = new VoxelPlacementSettings
|
||||
{
|
||||
PlacementMode = VoxelPlacementMode.Volumetric
|
||||
}
|
||||
};
|
||||
IsInVoxel = false;
|
||||
if (!CheckGridVoxelStatic(CubeGrid, settings))
|
||||
if (SubGrids.Count > 0 && SubGrids.All(e => !CheckGridVoxelStatic(e, settings)))
|
||||
return;
|
||||
IsInVoxel = true;
|
||||
}
|
||||
|
||||
public bool CheckGridVoxelStatic(MyCubeGrid grid, MyGridPlacementSettings settings)
|
||||
{
|
||||
if (!MyCubeGrid.IsAabbInsideVoxel(grid.WorldMatrix, grid.PositionComp.LocalAABB, settings)) return false;
|
||||
var worldAabb = grid.PositionComp.WorldAABB;
|
||||
return MyGamePruningStructure.AnyVoxelMapInBox(ref worldAabb) &&
|
||||
grid.GetBlocks().Any(block => MyCubeGrid.IsInVoxels(block, isVolumetric: false));
|
||||
}
|
||||
|
||||
private bool RecalculateSubgrids()
|
||||
{
|
||||
var previous = SubGrids;
|
||||
SubGrids = CubeGrid.GetConnectedGrids(GridLinkTypeEnum.Mechanical);
|
||||
SubGrids.Remove(CubeGrid);
|
||||
if (previous == null)
|
||||
{
|
||||
SubGrids.ForEach(OnSubGridAdded);
|
||||
return SubGrids.Any();
|
||||
}
|
||||
|
||||
var changed = false;
|
||||
var newSubGrids = SubGrids.Except(previous).ToList();
|
||||
if (newSubGrids.Count > 0)
|
||||
{
|
||||
changed = true;
|
||||
newSubGrids.ForEach(OnSubGridAdded);
|
||||
}
|
||||
|
||||
var removedSubGrids = previous.Except(SubGrids).ToList();
|
||||
if (removedSubGrids.Count > 0)
|
||||
{
|
||||
changed = true;
|
||||
removedSubGrids.ForEach(OnSubGridRemoved);
|
||||
}
|
||||
|
||||
if (changed) GlobalStatic.OcTreeHandler.GridTree.Update(Id);
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void OnGridSplit(MyCubeGrid arg1, MyCubeGrid arg2)
|
||||
{
|
||||
GlobalInstance.Log.Debug(
|
||||
$"Grid split Invoke: {arg1.DisplayName} and {arg2.DisplayName} {Thread.CurrentThread.Name}");
|
||||
GlobalInstance.Run(() =>
|
||||
{
|
||||
GlobalInstance.Log.Debug(
|
||||
$"Grid split Run: {arg1.DisplayName} and {arg2.DisplayName} {Thread.CurrentThread.Name}");
|
||||
GlobalStatic.OcTreeHandler.GridTree.Remove(arg1.EntityId);
|
||||
GlobalStatic.OcTreeHandler.GridTree.Remove(arg2.EntityId);
|
||||
GlobalStatic.OcTreeHandler.AddGrid(arg1);
|
||||
GlobalStatic.OcTreeHandler.AddGrid(arg2);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnGridMerged(MyCubeGrid arg1, MyCubeGrid arg2)
|
||||
{
|
||||
//GlobalInstance.Log.Info($"Merging {arg1.DisplayName} and {arg2.DisplayName}");
|
||||
RecalculateSubgrids();
|
||||
Recompute();
|
||||
Blocks.Clear();
|
||||
|
||||
FunctionalBlocks.Clear();
|
||||
ScanGridBlocks(CubeGrid);
|
||||
if (SubGrids.Count > 0)
|
||||
SubGrids.ForEach(ScanGridBlocks);
|
||||
}
|
||||
|
||||
private void OnConnectionChanged(MyCubeGrid arg1, GridLinkTypeEnum arg2)
|
||||
{
|
||||
if (Closing) return;
|
||||
if (arg2 != GridLinkTypeEnum.Mechanical) return;
|
||||
|
||||
RecalculateSubgrids();
|
||||
Recompute();
|
||||
Blocks.Clear();
|
||||
FunctionalBlocks.Clear();
|
||||
ScanGridBlocks(CubeGrid);
|
||||
if (SubGrids.Count > 0)
|
||||
SubGrids.ForEach(ScanGridBlocks);
|
||||
}
|
||||
|
||||
private void ScanGridBlocks(MyCubeGrid obj)
|
||||
{
|
||||
Parallel.ForEach(obj.GetFatBlocks(), AddBlock);
|
||||
}
|
||||
|
||||
private void OnSubGridAdded(MyCubeGrid grid)
|
||||
{
|
||||
GlobalStatic.OcTreeHandler.RemoveGrid(grid);
|
||||
grid.OnFatBlockAdded += AddBlock;
|
||||
grid.OnFatBlockRemoved += OnFatBlockRemoved;
|
||||
grid.OnStaticChanged += OnStaticChanged;
|
||||
grid.OnConnectionChanged += OnConnectionChanged;
|
||||
grid.OnGridMerge += OnGridMerged;
|
||||
grid.OnGridSplit += OnGridSplit;
|
||||
GridEvents.SubGridAdded?.Invoke(CubeGrid, grid);
|
||||
RecheckVoxelStatus();
|
||||
}
|
||||
|
||||
private void OnSubGridRemoved(MyCubeGrid grid)
|
||||
{
|
||||
grid.OnFatBlockAdded -= AddBlock;
|
||||
grid.OnFatBlockRemoved -= OnFatBlockRemoved;
|
||||
grid.OnStaticChanged -= OnStaticChanged;
|
||||
grid.OnConnectionChanged -= OnConnectionChanged;
|
||||
grid.OnGridMerge -= OnGridMerged;
|
||||
grid.OnGridSplit -= OnGridSplit;
|
||||
GridEvents.SubGridRemoved?.Invoke(CubeGrid, grid);
|
||||
RecheckVoxelStatus();
|
||||
}
|
||||
|
||||
private void AddBlock(MyCubeBlock block)
|
||||
{
|
||||
if (Closing) return;
|
||||
Blocks.AddOrUpdate(block.BlockDefinition.Id.TypeId,
|
||||
_ => ArrayList.Synchronized(new ArrayList { block }),
|
||||
(_, list) =>
|
||||
{
|
||||
list.Add(block);
|
||||
|
||||
return list;
|
||||
});
|
||||
|
||||
GlobalInstance.Run(() =>
|
||||
{
|
||||
if (block is IMyControllableEntity entity)
|
||||
{
|
||||
entity.ControllerInfo.ControlAcquired += OnControlAcquired;
|
||||
entity.ControllerInfo.ControlReleased += OnControlReleased;
|
||||
if (entity.ControllerInfo.IsLocallyControlled() || entity.ControllerInfo.IsRemotelyControlled())
|
||||
OnControlAcquired(entity.ControllerInfo.Controller);
|
||||
}
|
||||
|
||||
if (block is MyFunctionalBlock functionalBlock) FunctionalBlocks.Add(functionalBlock);
|
||||
|
||||
BlockEvents.OnBlockAdded?.Invoke(block);
|
||||
});
|
||||
}
|
||||
|
||||
private void OnControlAcquired(MyEntityController obj)
|
||||
{
|
||||
BlockEvents.OnPlayerControlAcquired?.Invoke(obj.ControlledEntity);
|
||||
}
|
||||
|
||||
private void OnControlReleased(MyEntityController obj)
|
||||
{
|
||||
if (obj != null) BlockEvents.OnPlayerControlReleased?.Invoke(obj.ControlledEntity);
|
||||
}
|
||||
|
||||
public void RaiseAttachedEntityChanged()
|
||||
{
|
||||
OnConnectionChanged(CubeGrid, GridLinkTypeEnum.Mechanical);
|
||||
}
|
||||
}
|
||||
}
|
17
GlobalShared/OcTree/Data/IOcTreeData.cs
Normal file
17
GlobalShared/OcTree/Data/IOcTreeData.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.OcTree.Data
|
||||
{
|
||||
public interface IOcTreeData : IDisposable
|
||||
{
|
||||
long Id { get; }
|
||||
List<long> ChildIds { get; }
|
||||
long Flags { get; }
|
||||
BoundingBoxD BoundingBox { get; }
|
||||
|
||||
void Update();
|
||||
void Recompute();
|
||||
}
|
||||
}
|
986
GlobalShared/OcTree/GenericOcTree.cs
Normal file
986
GlobalShared/OcTree/GenericOcTree.cs
Normal file
@@ -0,0 +1,986 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Global.Shared.OcTree.Data;
|
||||
using Global.Shared.Plugin;
|
||||
using Global.Shared.Util;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.OcTree
|
||||
{
|
||||
public class GenericOcTree<T> : IPoolable where T : IOcTreeData
|
||||
{
|
||||
private readonly GenericContainer<T> _bounds;
|
||||
|
||||
private readonly List<GenericContainer<T>> _containersToAdd = new List<GenericContainer<T>>();
|
||||
|
||||
private readonly Pool<GenericContainer<T>> _cPool =
|
||||
new Pool<GenericContainer<T>>(() => new GenericContainer<T>());
|
||||
|
||||
private readonly GenericOcTree<T>[] _nodes = new GenericOcTree<T>[8];
|
||||
|
||||
private readonly Pool<GenericOcTree<T>> _otPool = new Pool<GenericOcTree<T>>(() => new GenericOcTree<T>());
|
||||
|
||||
private int _capacity;
|
||||
private Bag<GenericContainer<T>> _containers;
|
||||
|
||||
private Dictionary<long, GenericContainer<T>> _idToContainer;
|
||||
private int _maxDepth;
|
||||
|
||||
private GenericOcTree<T> _parent;
|
||||
|
||||
private int treeDepth;
|
||||
|
||||
private GenericOcTree() : this(0, 0, 0, 0, 0, 0, 0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
public GenericOcTree(float x, float y, float z, float width, float height, float depth, int capacity,
|
||||
int maxDepth)
|
||||
{
|
||||
_bounds = new GenericContainer<T>().Set(x, y, z, width, height, depth);
|
||||
_capacity = capacity;
|
||||
_maxDepth = maxDepth;
|
||||
_containers = new Bag<GenericContainer<T>>(capacity);
|
||||
_idToContainer = new Dictionary<long, GenericContainer<T>>();
|
||||
}
|
||||
|
||||
public T this[long entityId] => Get(entityId);
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
for (var i = _containers.Size() - 1; i >= 0; i--) _cPool.Release(_containers.Remove(i));
|
||||
for (var i = 0; i < _nodes.Length; i++)
|
||||
if (_nodes[i] != null)
|
||||
{
|
||||
_otPool.Release(_nodes[i]);
|
||||
_nodes[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private GenericOcTree<T> Init(int currentTreeDepth, float x, float y, float z, float width, float height,
|
||||
float depth,
|
||||
GenericOcTree<T> parent)
|
||||
{
|
||||
treeDepth = currentTreeDepth;
|
||||
_parent = parent;
|
||||
_capacity = parent?._capacity ?? 1;
|
||||
_maxDepth = parent?._maxDepth ?? 6;
|
||||
_bounds.Set(x, y, z, width, height, depth);
|
||||
_idToContainer = parent?._idToContainer ?? new Dictionary<long, GenericContainer<T>>();
|
||||
_containers = new Bag<GenericContainer<T>>(_capacity);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private int IndexOf(float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
var midX = _bounds.X + _bounds.Width / 2;
|
||||
var midY = _bounds.Y + _bounds.Height / 2;
|
||||
var midZ = _bounds.Z + _bounds.Depth / 2;
|
||||
|
||||
var res = 0;
|
||||
if (x > midX) res |= E;
|
||||
else if (x < midX && x + width < midX) res |= W;
|
||||
else return OUTSIDE;
|
||||
|
||||
if (y > midY) res |= U;
|
||||
else if (y < midY && y + height < midY) res |= D;
|
||||
else return OUTSIDE;
|
||||
|
||||
if (z > midZ) res |= S;
|
||||
else if (z < midZ && z + depth < midZ) res |= N;
|
||||
else return OUTSIDE;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private bool ShouldMerge()
|
||||
{
|
||||
// If has no children, has nothing to merge
|
||||
if (_nodes[0] == null) return false;
|
||||
// If has any children with children, cannot merge
|
||||
if (_nodes.Any(n => n._nodes[0] != null)) return false;
|
||||
// If children combined contain less than capacity, can merge
|
||||
return _containers.Size() + _nodes.Sum(n => n._containers.Size()) <= _capacity;
|
||||
}
|
||||
|
||||
private bool ShouldSplit()
|
||||
{
|
||||
var halfWidth = _bounds.Width / 2;
|
||||
var halfHeight = _bounds.Height / 2;
|
||||
var halfDepth = _bounds.Depth / 2;
|
||||
var temporaryBounds = new GenericContainer<T>();
|
||||
temporaryBounds.Set(_bounds.X, _bounds.Y, _bounds.Z, halfWidth, halfHeight, halfDepth);
|
||||
if (_containers.All(e => temporaryBounds.Contains(e))) return false;
|
||||
temporaryBounds.Set(_bounds.X + halfWidth, _bounds.Y, _bounds.Z, halfWidth, halfHeight, halfDepth);
|
||||
if (_containers.All(e => temporaryBounds.Contains(e))) return false;
|
||||
temporaryBounds.Set(_bounds.X, _bounds.Y + halfHeight, _bounds.Z, halfWidth, halfHeight, halfDepth);
|
||||
if (_containers.All(e => temporaryBounds.Contains(e))) return false;
|
||||
temporaryBounds.Set(_bounds.X + halfWidth, _bounds.Y + halfHeight, _bounds.Z, halfWidth, halfHeight,
|
||||
halfDepth);
|
||||
if (_containers.All(e => temporaryBounds.Contains(e))) return false;
|
||||
temporaryBounds.Set(_bounds.X, _bounds.Y, _bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth);
|
||||
if (_containers.All(e => temporaryBounds.Contains(e))) return false;
|
||||
temporaryBounds.Set(_bounds.X + halfWidth, _bounds.Y, _bounds.Z + halfDepth, halfWidth, halfHeight,
|
||||
halfDepth);
|
||||
if (_containers.All(e => temporaryBounds.Contains(e))) return false;
|
||||
temporaryBounds.Set(_bounds.X, _bounds.Y + halfHeight, _bounds.Z + halfDepth, halfWidth, halfHeight,
|
||||
halfDepth);
|
||||
if (_containers.All(e => temporaryBounds.Contains(e))) return false;
|
||||
temporaryBounds.Set(_bounds.X + halfWidth, _bounds.Y + halfHeight, _bounds.Z + halfDepth, halfWidth,
|
||||
halfHeight, halfDepth);
|
||||
if (_containers.All(e => temporaryBounds.Contains(e))) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Split()
|
||||
{
|
||||
// if (!ShouldSplit()) return; TODO: See if necessary
|
||||
|
||||
var halfWidth = _bounds.Width / 2;
|
||||
var halfHeight = _bounds.Height / 2;
|
||||
var halfDepth = _bounds.Depth / 2;
|
||||
|
||||
_nodes[DNW] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y, _bounds.Z, halfWidth, halfHeight,
|
||||
halfDepth, this);
|
||||
_nodes[DNE] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y, _bounds.Z, halfWidth,
|
||||
halfHeight, halfDepth, this);
|
||||
_nodes[DSW] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y, _bounds.Z + halfDepth, halfWidth,
|
||||
halfHeight, halfDepth, this);
|
||||
_nodes[DSE] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y,
|
||||
_bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this);
|
||||
|
||||
_nodes[UNW] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y + halfHeight, _bounds.Z,
|
||||
halfWidth, halfHeight, halfDepth, this);
|
||||
_nodes[UNE] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y + halfHeight,
|
||||
_bounds.Z, halfWidth, halfHeight, halfDepth, this);
|
||||
_nodes[USW] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y + halfHeight,
|
||||
_bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this);
|
||||
_nodes[USE] = _otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y + halfHeight,
|
||||
_bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this);
|
||||
}
|
||||
|
||||
private void HandleMerge()
|
||||
{
|
||||
if (!ShouldMerge()) return;
|
||||
|
||||
for (var index = _nodes.Length - 1; index >= 0; index--)
|
||||
{
|
||||
var ocTree = _nodes[index];
|
||||
for (var i = ocTree._containers.Size() - 1; i >= 0; i--)
|
||||
{
|
||||
var ocTreeContainer = ocTree._containers[i];
|
||||
_containersToAdd.Add(ocTreeContainer);
|
||||
ocTree._containers.Remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (var index = _nodes.Length - 1; index >= 0; index--)
|
||||
{
|
||||
var ocTree = _nodes[index];
|
||||
_nodes[index] = null;
|
||||
_otPool.Release(ocTree);
|
||||
ocTree.Reset();
|
||||
}
|
||||
|
||||
foreach (var container in _containersToAdd) Insert(container);
|
||||
|
||||
_containersToAdd.Clear();
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetAll()
|
||||
{
|
||||
if (_nodes[0] != null)
|
||||
foreach (var data in _nodes.SelectMany(e => e.GetAll()))
|
||||
yield return data;
|
||||
foreach (var genericContainer in _containers)
|
||||
if (genericContainer.Value != null)
|
||||
yield return genericContainer.Value;
|
||||
}
|
||||
|
||||
#region Node Bitwise
|
||||
|
||||
private const int OUTSIDE = -1;
|
||||
private const int D = 0b000;
|
||||
private const int U = 0b100;
|
||||
private const int S = 0b000;
|
||||
private const int N = 0b010;
|
||||
private const int W = 0b000;
|
||||
private const int E = 0b001;
|
||||
private const int DSW = D | S | W; // 0
|
||||
private const int DSE = D | S | E; // 1
|
||||
private const int DNW = D | N | W; // 2
|
||||
private const int DNE = D | N | E; // 3
|
||||
private const int USW = U | S | W; // 4
|
||||
private const int USE = U | S | E; // 5
|
||||
private const int UNW = U | N | W; // 6
|
||||
private const int UNE = U | N | E; // 7
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modifiers
|
||||
|
||||
public void Update(long entityId)
|
||||
{
|
||||
if (!Contains(entityId)) return;
|
||||
var c = _idToContainer[entityId];
|
||||
try
|
||||
{
|
||||
var (removedChildren, addedChildren) = c.Update();
|
||||
if (c.EntityId != entityId)
|
||||
{
|
||||
var old = entityId;
|
||||
entityId = c.EntityId;
|
||||
// already contains new id.. just give up
|
||||
if (Contains(entityId)) return;
|
||||
// Entity id changed, update the octree
|
||||
_idToContainer.Remove(old);
|
||||
_idToContainer.Add(entityId, c);
|
||||
}
|
||||
|
||||
// Attempting to update subgrid
|
||||
if (removedChildren != null)
|
||||
foreach (var removedChild in removedChildren)
|
||||
_idToContainer.Remove(removedChild);
|
||||
if (addedChildren != null)
|
||||
foreach (var addedChild in addedChildren)
|
||||
_idToContainer.Add(addedChild, c);
|
||||
|
||||
var parentTree = c.Parent;
|
||||
if (parentTree._bounds.Contains(c)) return;
|
||||
|
||||
parentTree._containers.Remove(c);
|
||||
while (parentTree._parent != null && !parentTree._bounds.Contains(c)) parentTree = parentTree._parent;
|
||||
|
||||
parentTree.Insert(c);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
GlobalInstance.Log.Critical(e, "Failed to update octree");
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(T value)
|
||||
{
|
||||
Insert(_cPool.Obtain().Set(value));
|
||||
}
|
||||
|
||||
private void Insert(GenericContainer<T> c)
|
||||
{
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(c.X, c.Y, c.Z, c.Width, c.Height, c.Depth);
|
||||
if (index != OUTSIDE)
|
||||
{
|
||||
_nodes[index].Insert(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
c.Parent = this;
|
||||
_idToContainer[c.EntityId] = c;
|
||||
if (c.ChildIds != null)
|
||||
foreach (var childId in c.ChildIds)
|
||||
_idToContainer[childId] = c;
|
||||
_containers.Add(c);
|
||||
|
||||
// If under capacity or at/over max depth, we're done.
|
||||
if (_containers.Size() <= _capacity || treeDepth >= _maxDepth) return;
|
||||
|
||||
if (_nodes[0] == null) Split();
|
||||
if (_nodes[0] == null) return;
|
||||
|
||||
var items = _containers.data;
|
||||
for (var i = _containers.Size() - 1; i >= 0; i--)
|
||||
{
|
||||
var next = items[i];
|
||||
if (next == null) continue;
|
||||
var index = IndexOf(next.X, next.Y, next.Z, next.Width, next.Height, next.Depth);
|
||||
if (index == OUTSIDE) continue;
|
||||
_nodes[index].Insert(next);
|
||||
_containers.Remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(long entityId)
|
||||
{
|
||||
if (!_idToContainer.ContainsKey(entityId)) return;
|
||||
|
||||
var container = _idToContainer[entityId];
|
||||
var parent = container.Parent;
|
||||
parent?._containers.Remove(container);
|
||||
_idToContainer.Remove(entityId);
|
||||
if (container.ChildIds != null)
|
||||
foreach (var childId in container.ChildIds)
|
||||
_idToContainer.Remove(childId);
|
||||
container.Value?.Dispose();
|
||||
container.Value = default;
|
||||
_cPool.Release(container);
|
||||
parent?._parent?.HandleMerge();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Getters
|
||||
|
||||
public bool Contains(long entityId)
|
||||
{
|
||||
return _idToContainer.ContainsKey(entityId);
|
||||
}
|
||||
|
||||
public T Get(long entityId)
|
||||
{
|
||||
return !Contains(entityId) ? default : _idToContainer[entityId].Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns entityIds of entities that are inside the OcTrees that contain given point.
|
||||
/// </summary>
|
||||
/// <param name="entityIds">List to fill</param>
|
||||
/// <param name="x">X position</param>
|
||||
/// <param name="y">Y position</param>
|
||||
/// <param name="z">Z position</param>
|
||||
public List<long> Get(List<long> entityIds, float x, float y, float z)
|
||||
{
|
||||
if (!_bounds.Contains(x, y, z)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, 0f, 0f, 0f);
|
||||
if (index != OUTSIDE) _nodes[index].Get(entityIds, x, y, z);
|
||||
}
|
||||
|
||||
entityIds.AddRange(_containers.Select(container => container.EntityId));
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, float x, float y, float z, long flags)
|
||||
{
|
||||
if (flags == 0L) return Get(entityIds, x, y, z);
|
||||
if (!_bounds.Contains(x, y, z)) return entityIds;
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, 0f, 0f, 0f);
|
||||
if (index != OUTSIDE) _nodes[index].Get(entityIds, x, y, z, flags);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags)
|
||||
select container.EntityId
|
||||
);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, BoundingSphereD sphereD)
|
||||
{
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].Get(entityIds, sphereD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.Get(entityIds, sphereD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(_containers.Select(container => container.EntityId));
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, long flags, BoundingSphereD sphereD)
|
||||
{
|
||||
if (flags == 0L) return Get(entityIds, sphereD);
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].Get(entityIds, flags, sphereD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.Get(entityIds, flags, sphereD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags)
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
//TODO: Make exact check sphere rather than box
|
||||
public List<long> GetExact(List<long> entityIds, BoundingSphereD sphereD)
|
||||
{
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].GetExact(entityIds, sphereD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.GetExact(entityIds, sphereD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.Overlaps(x, y, z, width, height, depth) && sphereD.Intersects(
|
||||
new BoundingBoxD(new Vector3D(container.X, container.Y, container.Z),
|
||||
new Vector3D(
|
||||
container.X + container.Width,
|
||||
container.Y + container.Height,
|
||||
container.Z + container.Depth
|
||||
)))
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, long flags, BoundingSphereD sphereD)
|
||||
{
|
||||
if (flags == 0L) return GetExact(entityIds, sphereD);
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].GetExact(entityIds, flags, sphereD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.GetExact(entityIds, flags, sphereD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags) &&
|
||||
container.Overlaps(x, y, z, width, height, depth) &&
|
||||
sphereD.Intersects(
|
||||
new BoundingBoxD(new Vector3D(container.X, container.Y, container.Z),
|
||||
new Vector3D(
|
||||
container.X + container.Width,
|
||||
container.Y + container.Height,
|
||||
container.Z + container.Depth
|
||||
)))
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, BoundingBoxD boxD)
|
||||
{
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].Get(entityIds, boxD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.Get(entityIds, boxD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(_containers.Select(container => container.EntityId));
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, long flags, BoundingBoxD boxD)
|
||||
{
|
||||
if (flags == 0L) return Get(entityIds, boxD);
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].Get(entityIds, flags, boxD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.Get(entityIds, flags, boxD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags) && container.Overlaps(x, y, z, width, height, depth)
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, MyOrientedBoundingBoxD boxBoundingBox)
|
||||
{
|
||||
var boxD = boxBoundingBox.GetAABB();
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].GetExact(entityIds, boxD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.GetExact(entityIds, boxD);
|
||||
}
|
||||
|
||||
foreach (var container in _containers)
|
||||
{
|
||||
if (!container.Overlaps(x, y, z, width, height, depth)) continue;
|
||||
|
||||
var containerBox = container.BoundingBox;
|
||||
if (boxBoundingBox.Intersects(ref containerBox)) entityIds.Add(container.EntityId);
|
||||
}
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, long flags, MyOrientedBoundingBoxD boxBoundingBox)
|
||||
{
|
||||
if (flags == 0L) return GetExact(entityIds, boxBoundingBox);
|
||||
var boxD = boxBoundingBox.GetAABB();
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].GetExact(entityIds, boxD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.GetExact(entityIds, boxD);
|
||||
}
|
||||
|
||||
foreach (var container in _containers)
|
||||
{
|
||||
if (!container.MatchesFlags(flags) ||
|
||||
!container.Overlaps(x, y, z, width, height, depth)) continue;
|
||||
|
||||
var containerBox = container.BoundingBox;
|
||||
if (boxBoundingBox.Intersects(ref containerBox)) entityIds.Add(container.EntityId);
|
||||
}
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, BoundingBoxD boxD)
|
||||
{
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].GetExact(entityIds, boxD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.GetExact(entityIds, boxD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.Overlaps(x, y, z, width, height, depth)
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, long flags, BoundingBoxD boxD)
|
||||
{
|
||||
if (flags == 0L) return GetExact(entityIds, boxD);
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].GetExact(entityIds, flags, boxD);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.GetExact(entityIds, flags, boxD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from GenericContainer in _containers
|
||||
where GenericContainer.MatchesFlags(flags) && GenericContainer.Overlaps(x, y, z, width, height, depth)
|
||||
select GenericContainer.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
|
||||
public void GetExact(List<T> outList, BoundingSphereD sphereD, long flags = 0L)
|
||||
{
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].GetExact(outList, sphereD, flags);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.GetExact(outList, sphereD, flags);
|
||||
}
|
||||
|
||||
if (_containers.Any())
|
||||
outList.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags) &&
|
||||
container.Overlaps(x, y, z, width, height, depth) &&
|
||||
sphereD.Intersects(container.BoundingBox)
|
||||
select container.Value);
|
||||
}
|
||||
|
||||
public void GetExact(List<T> outList, BoundingBoxD box, long flags = 0L)
|
||||
{
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].GetExact(outList, box, flags);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.GetExact(outList, box, flags);
|
||||
}
|
||||
|
||||
if (_containers.Any())
|
||||
outList.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags) &&
|
||||
container.Overlaps(x, y, z, width, height, depth) &&
|
||||
box.Intersects(container.BoundingBox)
|
||||
select container.Value);
|
||||
}
|
||||
|
||||
public void GetExact(List<T> outList, MyOrientedBoundingBoxD boxD, long flags = 0L)
|
||||
{
|
||||
var box = boxD.GetAABB();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) _nodes[index].GetExact(outList, box, flags);
|
||||
else
|
||||
foreach (var ocTree in _nodes)
|
||||
ocTree.GetExact(outList, box, flags);
|
||||
}
|
||||
|
||||
if (_containers.Any())
|
||||
// ReSharper disable once LoopCanBeConvertedToQuery
|
||||
foreach (var container in _containers)
|
||||
{
|
||||
if (!container.MatchesFlags(flags) || !container.Overlaps(x, y, z, width, height, depth)) continue;
|
||||
var containerBox = container.BoundingBox;
|
||||
if (boxD.Intersects(ref containerBox)) outList.Add(container.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<T> GetEnumerable(BoundingSphereD sphereD, long flags = 0L)
|
||||
{
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) yield break;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE)
|
||||
foreach (var data in _nodes[index].GetEnumerable(sphereD, flags))
|
||||
yield return data;
|
||||
else
|
||||
foreach (var data in _nodes.SelectMany(e => e.GetEnumerable(sphereD, flags)))
|
||||
yield return data;
|
||||
}
|
||||
|
||||
if (!_containers.Any()) yield break;
|
||||
|
||||
foreach (var c in _containers)
|
||||
if (c.MatchesFlags(flags) && c.Overlaps(x, y, z, width, height, depth) &&
|
||||
sphereD.Intersects(c.BoundingBox))
|
||||
yield return c.Value;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<T> GetEnumerable(BoundingBoxD box, long flags = 0L)
|
||||
{
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) yield break;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE)
|
||||
foreach (var data in _nodes[index].GetEnumerable(box, flags))
|
||||
yield return data;
|
||||
else
|
||||
foreach (var data in _nodes.SelectMany(e => e.GetEnumerable(box, flags)))
|
||||
yield return data;
|
||||
}
|
||||
|
||||
if (!_containers.Any()) yield break;
|
||||
|
||||
foreach (var c in _containers)
|
||||
if (c.MatchesFlags(flags) && c.Overlaps(x, y, z, width, height, depth) && box.Intersects(c.BoundingBox))
|
||||
yield return c.Value;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<T> GetEnumerable(MyOrientedBoundingBoxD boxD, long flags = 0L)
|
||||
{
|
||||
var box = boxD.GetAABB();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) yield break;
|
||||
|
||||
if (_nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE)
|
||||
foreach (var data in _nodes[index].GetEnumerable(boxD, flags))
|
||||
yield return data;
|
||||
else
|
||||
foreach (var data in _nodes.SelectMany(e => e.GetEnumerable(boxD, flags)))
|
||||
yield return data;
|
||||
}
|
||||
|
||||
if (!_containers.Any()) yield break;
|
||||
|
||||
foreach (var c in _containers)
|
||||
{
|
||||
var cBox = c.BoundingBox;
|
||||
if (c.MatchesFlags(flags) && c.Overlaps(x, y, z, width, height, depth) && boxD.Intersects(ref cBox))
|
||||
yield return c.Value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class GenericContainer<T> : IPoolable, IContainer where T : IOcTreeData
|
||||
{
|
||||
private static readonly BoundingBoxD ReusableBox = new BoundingBoxD();
|
||||
public List<long> ChildIds;
|
||||
public long EntityId;
|
||||
|
||||
public long Flags;
|
||||
public GenericOcTree<T> Parent;
|
||||
|
||||
public T Value;
|
||||
|
||||
public BoundingBoxD BoundingBox => ReusableBox.SetToContainer(this);
|
||||
public float X { get; private set; }
|
||||
public float Y { get; private set; }
|
||||
public float Z { get; private set; }
|
||||
public float Width { get; private set; }
|
||||
public float Height { get; private set; }
|
||||
public float Depth { get; private set; }
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ChildIds = null;
|
||||
EntityId = -1;
|
||||
Flags = 0;
|
||||
X = 0;
|
||||
Y = 0;
|
||||
Z = 0;
|
||||
Width = 0;
|
||||
Height = 0;
|
||||
Depth = 0;
|
||||
Value = default;
|
||||
}
|
||||
|
||||
public GenericContainer<T> Set(T value)
|
||||
{
|
||||
Value = value;
|
||||
ChildIds = value.ChildIds;
|
||||
EntityId = value.Id;
|
||||
Flags = value.Flags;
|
||||
var box = value.BoundingBox;
|
||||
X = (float)box.Min.X;
|
||||
Y = (float)box.Min.Y;
|
||||
Z = (float)box.Min.Z;
|
||||
Width = (float)box.Max.X - X;
|
||||
Height = (float)box.Max.Y - Y;
|
||||
Depth = (float)box.Max.Z - Z;
|
||||
return this;
|
||||
}
|
||||
|
||||
public KeyValuePair<IEnumerable<long>, IEnumerable<long>> Update()
|
||||
{
|
||||
if (Value == null) return new KeyValuePair<IEnumerable<long>, IEnumerable<long>>(null, null);
|
||||
Value.Update();
|
||||
var previousChildIds = ChildIds;
|
||||
ChildIds = Value.ChildIds;
|
||||
Flags = Value.Flags;
|
||||
var box = Value.BoundingBox;
|
||||
X = (float)box.Min.X;
|
||||
Y = (float)box.Min.Y;
|
||||
Z = (float)box.Min.Z;
|
||||
Width = (float)box.Max.X - X;
|
||||
Height = (float)box.Max.Y - Y;
|
||||
Depth = (float)box.Max.Z - Z;
|
||||
return new KeyValuePair<IEnumerable<long>, IEnumerable<long>>(
|
||||
previousChildIds.Where(e => !ChildIds.Contains(e)),
|
||||
ChildIds.Where(e => !previousChildIds.Contains(e)));
|
||||
}
|
||||
|
||||
public GenericContainer<T> Set(float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Depth = depth;
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool MatchesFlags(long flags)
|
||||
{
|
||||
return flags == 0 || (Flags & flags) > 0;
|
||||
}
|
||||
|
||||
public bool Contains(float x, float y, float z)
|
||||
{
|
||||
return X <= x && x <= X + Width &&
|
||||
Y <= y && y <= Y + Height &&
|
||||
Z <= z && z <= Z + Depth;
|
||||
}
|
||||
|
||||
public bool Contains(float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
var xMax = x + width;
|
||||
var yMax = x + height;
|
||||
var zMax = x + depth;
|
||||
|
||||
return x > X && x < X + Width && xMax > X && xMax < X + Width &&
|
||||
y > Y && y < Y + Height && yMax > Y && yMax < Y + Height &&
|
||||
z > Z && z < Z + Depth && zMax > Z && zMax < Z + Depth;
|
||||
}
|
||||
|
||||
public bool Overlaps(float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
return !(X > x + width || x > X + Width || Y > y + height || y > Y + Height || Z > z + depth ||
|
||||
z > Z + Depth);
|
||||
}
|
||||
|
||||
public bool IntersectsRay(Vector3 start, Vector3 end)
|
||||
{
|
||||
var lineD = new LineD(start, end);
|
||||
var box = BoundingBox;
|
||||
return box.Intersects(ref lineD);
|
||||
}
|
||||
|
||||
public bool Contains(GenericContainer<T> c)
|
||||
{
|
||||
return Contains(c.X, c.Y, c.Z, c.Width, c.Height, c.Depth);
|
||||
}
|
||||
}
|
||||
}
|
12
GlobalShared/OcTree/IContainer.cs
Normal file
12
GlobalShared/OcTree/IContainer.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Global.Shared.OcTree
|
||||
{
|
||||
public interface IContainer
|
||||
{
|
||||
float X { get; }
|
||||
float Y { get; }
|
||||
float Z { get; }
|
||||
float Width { get; }
|
||||
float Height { get; }
|
||||
float Depth { get; }
|
||||
}
|
||||
}
|
707
GlobalShared/OcTree/OcTree.cs
Normal file
707
GlobalShared/OcTree/OcTree.cs
Normal file
@@ -0,0 +1,707 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.OcTree
|
||||
{
|
||||
public class OcTree : IPoolable
|
||||
{
|
||||
private static int TotalIds;
|
||||
private static readonly Pool<Container> cPool = new Pool<Container>(() => new Container());
|
||||
private static readonly Pool<OcTree> otPool = new Pool<OcTree>(() => new OcTree());
|
||||
|
||||
private readonly Container _bounds;
|
||||
|
||||
private readonly List<Container> _containersToAdd = new List<Container>();
|
||||
private readonly OcTree[] nodes = new OcTree[8];
|
||||
private Bag<Container> _containers;
|
||||
|
||||
private int capacity;
|
||||
|
||||
private Dictionary<long, Container> idToContainer;
|
||||
private int maxDepth;
|
||||
|
||||
private OcTree parent;
|
||||
|
||||
private BoundingBoxD reusableBox = new BoundingBoxD();
|
||||
private int treeDepth;
|
||||
|
||||
private OcTree() : this(0, 0, 0, 0, 0, 0, 0, 0)
|
||||
{
|
||||
}
|
||||
|
||||
public OcTree(float x, float y, float z, float width, float height, float depth, int capacity,
|
||||
int maxDepth)
|
||||
{
|
||||
_bounds = new Container().Set(x, y, z, width, height, depth);
|
||||
this.capacity = capacity;
|
||||
this.maxDepth = maxDepth;
|
||||
_containers = new Bag<Container>(capacity);
|
||||
idToContainer = new Dictionary<long, Container>();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
for (var i = _containers.Size() - 1; i >= 0; i--) cPool.Release(_containers.Remove(i));
|
||||
for (var i = 0; i < nodes.Length; i++)
|
||||
if (nodes[i] != null)
|
||||
{
|
||||
otPool.Release(nodes[i]);
|
||||
nodes[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private OcTree Init(int currentTreeDepth, float x, float y, float z, float width, float height, float depth,
|
||||
OcTree parent)
|
||||
{
|
||||
treeDepth = currentTreeDepth;
|
||||
this.parent = parent;
|
||||
capacity = parent?.capacity ?? 1;
|
||||
maxDepth = parent?.maxDepth ?? 6;
|
||||
_bounds.Set(x, y, z, width, height, depth);
|
||||
idToContainer = parent?.idToContainer ?? new Dictionary<long, Container>();
|
||||
_containers = new Bag<Container>(capacity);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private int IndexOf(float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
var midX = _bounds.X + _bounds.Width / 2;
|
||||
var midY = _bounds.Y + _bounds.Height / 2;
|
||||
var midZ = _bounds.Z + _bounds.Depth / 2;
|
||||
|
||||
var res = 0;
|
||||
if (x > midX) res |= E;
|
||||
else if (x < midX && x + width < midX) res |= W;
|
||||
else return OUTSIDE;
|
||||
|
||||
if (y > midY) res |= U;
|
||||
else if (y < midY && y + height < midY) res |= D;
|
||||
else return OUTSIDE;
|
||||
|
||||
if (z > midZ) res |= S;
|
||||
else if (z < midZ && z + depth < midZ) res |= N;
|
||||
else return OUTSIDE;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private bool ShouldMerge()
|
||||
{
|
||||
// If has no children, has nothing to merge
|
||||
if (nodes[0] == null) return false;
|
||||
// If has any children with children, cannot merge
|
||||
if (nodes.Any(n => n.nodes[0] != null)) return false;
|
||||
// If children combined contain less than capacity, can merge
|
||||
return _containers.Size() + nodes.Sum(n => n._containers.Size()) <= capacity;
|
||||
}
|
||||
|
||||
private void Split()
|
||||
{
|
||||
var halfWidth = _bounds.Width / 2;
|
||||
var halfHeight = _bounds.Height / 2;
|
||||
var halfDepth = _bounds.Depth / 2;
|
||||
nodes[DNW] = otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y, _bounds.Z, halfWidth, halfHeight,
|
||||
halfDepth, this);
|
||||
nodes[DNE] = otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y, _bounds.Z, halfWidth,
|
||||
halfHeight, halfDepth, this);
|
||||
nodes[DSW] = otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y, _bounds.Z + halfDepth, halfWidth,
|
||||
halfHeight, halfDepth, this);
|
||||
nodes[DSE] = otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y,
|
||||
_bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this);
|
||||
|
||||
nodes[UNW] = otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y + halfHeight, _bounds.Z,
|
||||
halfWidth, halfHeight, halfDepth, this);
|
||||
nodes[UNE] = otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y + halfHeight,
|
||||
_bounds.Z, halfWidth, halfHeight, halfDepth, this);
|
||||
nodes[USW] = otPool.Obtain().Init(treeDepth + 1, _bounds.X, _bounds.Y + halfHeight,
|
||||
_bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this);
|
||||
nodes[USE] = otPool.Obtain().Init(treeDepth + 1, _bounds.X + halfWidth, _bounds.Y + halfHeight,
|
||||
_bounds.Z + halfDepth, halfWidth, halfHeight, halfDepth, this);
|
||||
}
|
||||
|
||||
private void HandleMerge()
|
||||
{
|
||||
if (!ShouldMerge()) return;
|
||||
|
||||
for (var index = nodes.Length - 1; index >= 0; index--)
|
||||
{
|
||||
var ocTree = nodes[index];
|
||||
for (var i = ocTree._containers.Size() - 1; i >= 0; i--)
|
||||
{
|
||||
var ocTreeContainer = ocTree._containers[i];
|
||||
_containersToAdd.Add(ocTreeContainer);
|
||||
ocTree._containers.Remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (var index = nodes.Length - 1; index >= 0; index--)
|
||||
{
|
||||
var ocTree = nodes[index];
|
||||
nodes[index] = null;
|
||||
otPool.Release(ocTree);
|
||||
ocTree.Reset();
|
||||
}
|
||||
|
||||
foreach (var container in _containersToAdd) Insert(container);
|
||||
|
||||
_containersToAdd.Clear();
|
||||
}
|
||||
|
||||
#region Node Bitwise
|
||||
|
||||
private const int OUTSIDE = -1;
|
||||
private const int D = 0b000;
|
||||
private const int U = 0b100;
|
||||
private const int S = 0b000;
|
||||
private const int N = 0b010;
|
||||
private const int W = 0b000;
|
||||
private const int E = 0b001;
|
||||
private const int DSW = D | S | W; // 0
|
||||
private const int DSE = D | S | E; // 1
|
||||
private const int DNW = D | N | W; // 2
|
||||
private const int DNE = D | N | E; // 3
|
||||
private const int USW = U | S | W; // 4
|
||||
private const int USE = U | S | E; // 5
|
||||
private const int UNW = U | N | W; // 6
|
||||
private const int UNE = U | N | E; // 7
|
||||
|
||||
#endregion
|
||||
|
||||
#region Modifiers
|
||||
|
||||
public void Upsert(long entityId, long flags, BoundingBoxD aabb)
|
||||
{
|
||||
Upsert(entityId, flags, (float)aabb.Min.X, (float)aabb.Min.Y, (float)aabb.Min.Z,
|
||||
(float)aabb.Max.X - (float)aabb.Min.X, (float)aabb.Max.Y - (float)aabb.Min.Y,
|
||||
(float)aabb.Max.Z - (float)aabb.Min.Z);
|
||||
}
|
||||
|
||||
public void Upsert(long entityId, float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
Upsert(entityId, 0L, x, y, z, width, height, depth);
|
||||
}
|
||||
|
||||
public void Upsert(long entityId, long flags, float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
Container container = null;
|
||||
if (idToContainer.ContainsKey(entityId)) container = idToContainer[entityId];
|
||||
if (container != null && container.EntityId != -1)
|
||||
{
|
||||
container.Flags |= flags;
|
||||
Update(entityId, x, y, z, width, height, depth);
|
||||
return;
|
||||
}
|
||||
|
||||
Insert(entityId, flags, x, y, z, width, height, depth);
|
||||
}
|
||||
|
||||
private void Update(long entityId, float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
var c = idToContainer[entityId];
|
||||
c.Set(entityId, c.Flags, x, y, z, width, height, depth);
|
||||
|
||||
var parentTree = c.Parent;
|
||||
if (parentTree._bounds.Contains(c)) return;
|
||||
|
||||
parentTree._containers.Remove(c);
|
||||
while (parentTree.parent != null && !parentTree._bounds.Contains(c)) parentTree = parentTree.parent;
|
||||
|
||||
parentTree.Insert(c);
|
||||
}
|
||||
|
||||
private void Insert(long entityId, float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
Insert(entityId, 0L, x, y, z, width, height, depth);
|
||||
}
|
||||
|
||||
private void Insert(long entityId, long flags, float x, float y, float z, float width, float height,
|
||||
float depth)
|
||||
{
|
||||
Insert(cPool.Obtain().Set(entityId, flags, x, y, z, width, height, depth));
|
||||
}
|
||||
|
||||
private void Insert(Container c)
|
||||
{
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(c.X, c.Y, c.Z, c.Width, c.Height, c.Depth);
|
||||
if (index != OUTSIDE)
|
||||
{
|
||||
nodes[index].Insert(c);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
c.Parent = this;
|
||||
idToContainer[c.EntityId] = c;
|
||||
_containers.Add(c);
|
||||
|
||||
if (_containers.Size() <= capacity || treeDepth >= maxDepth) return;
|
||||
|
||||
if (nodes[0] == null) Split();
|
||||
|
||||
var items = _containers.data;
|
||||
for (var i = _containers.Size() - 1; i >= 0; i--)
|
||||
{
|
||||
var next = items[i];
|
||||
if (next == null) continue;
|
||||
var index = IndexOf(next.X, next.Y, next.Z, next.Width, next.Height, next.Depth);
|
||||
if (index == OUTSIDE) continue;
|
||||
nodes[index].Insert(next);
|
||||
_containers.Remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(long entityId)
|
||||
{
|
||||
if (!idToContainer.ContainsKey(entityId)) return;
|
||||
|
||||
var container = idToContainer[entityId];
|
||||
var parent = container.Parent;
|
||||
parent?._containers.Remove(container);
|
||||
idToContainer.Remove(entityId);
|
||||
cPool.Release(container);
|
||||
parent?.parent?.HandleMerge();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Getters
|
||||
|
||||
/// <summary>
|
||||
/// Returns entityIds of entities that are inside the OcTrees that contain given point.
|
||||
/// </summary>
|
||||
/// <param name="entityIds">List to fill</param>
|
||||
/// <param name="x">X position</param>
|
||||
/// <param name="y">Y position</param>
|
||||
/// <param name="z">Z position</param>
|
||||
public List<long> Get(List<long> entityIds, float x, float y, float z)
|
||||
{
|
||||
if (!_bounds.Contains(x, y, z)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, 0f, 0f, 0f);
|
||||
if (index != OUTSIDE) nodes[index].Get(entityIds, x, y, z);
|
||||
}
|
||||
|
||||
entityIds.AddRange(_containers.Select(container => container.EntityId));
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, float x, float y, float z, long flags)
|
||||
{
|
||||
if (flags == 0L) return Get(entityIds, x, y, z);
|
||||
if (!_bounds.Contains(x, y, z)) return entityIds;
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, 0f, 0f, 0f);
|
||||
if (index != OUTSIDE) nodes[index].Get(entityIds, x, y, z, flags);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags)
|
||||
select container.EntityId
|
||||
);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, BoundingSphereD sphereD)
|
||||
{
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].Get(entityIds, sphereD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.Get(entityIds, sphereD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(_containers.Select(container => container.EntityId));
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, long flags, BoundingSphereD sphereD)
|
||||
{
|
||||
if (flags == 0L) return Get(entityIds, sphereD);
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].Get(entityIds, flags, sphereD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.Get(entityIds, flags, sphereD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags)
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
//TODO: Make exact check sphere rather than box
|
||||
public List<long> GetExact(List<long> entityIds, BoundingSphereD sphereD)
|
||||
{
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].GetExact(entityIds, sphereD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.GetExact(entityIds, sphereD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.Overlaps(x, y, z, width, height, depth) && sphereD.Intersects(
|
||||
new BoundingBoxD(new Vector3D(container.X, container.Y, container.Z),
|
||||
new Vector3D(
|
||||
container.X + container.Width,
|
||||
container.Y + container.Height,
|
||||
container.Z + container.Depth
|
||||
)))
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, long flags, BoundingSphereD sphereD)
|
||||
{
|
||||
if (flags == 0L) return GetExact(entityIds, sphereD);
|
||||
var box = sphereD.GetBoundingBox();
|
||||
var x = (float)box.Min.X;
|
||||
var y = (float)box.Min.Y;
|
||||
var z = (float)box.Min.Z;
|
||||
var width = (float)box.Max.X - x;
|
||||
var height = (float)box.Max.Y - y;
|
||||
var depth = (float)box.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].GetExact(entityIds, flags, sphereD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.GetExact(entityIds, flags, sphereD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags) && container.Overlaps(x, y, z, width, height, depth) &&
|
||||
sphereD.Intersects(
|
||||
new BoundingBoxD(new Vector3D(container.X, container.Y, container.Z),
|
||||
new Vector3D(
|
||||
container.X + container.Width,
|
||||
container.Y + container.Height,
|
||||
container.Z + container.Depth
|
||||
)))
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, BoundingBoxD boxD)
|
||||
{
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].Get(entityIds, boxD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.Get(entityIds, boxD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(_containers.Select(container => container.EntityId));
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> Get(List<long> entityIds, long flags, BoundingBoxD boxD)
|
||||
{
|
||||
if (flags == 0L) return Get(entityIds, boxD);
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].Get(entityIds, flags, boxD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.Get(entityIds, flags, boxD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags) && container.Overlaps(x, y, z, width, height, depth)
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, MyOrientedBoundingBoxD boxBoundingBox)
|
||||
{
|
||||
var boxD = boxBoundingBox.GetAABB();
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].GetExact(entityIds, boxD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.GetExact(entityIds, boxD);
|
||||
}
|
||||
|
||||
foreach (var container in _containers)
|
||||
{
|
||||
if (!container.Overlaps(x, y, z, width, height, depth)) continue;
|
||||
|
||||
var containerBox = container.BoundingBox;
|
||||
if (boxBoundingBox.Intersects(ref containerBox)) entityIds.Add(container.EntityId);
|
||||
}
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, long flags, MyOrientedBoundingBoxD boxBoundingBox)
|
||||
{
|
||||
if (flags == 0L) return GetExact(entityIds, boxBoundingBox);
|
||||
var boxD = boxBoundingBox.GetAABB();
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].GetExact(entityIds, boxD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.GetExact(entityIds, boxD);
|
||||
}
|
||||
|
||||
foreach (var container in _containers)
|
||||
{
|
||||
if (!container.MatchesFlags(flags) || !container.Overlaps(x, y, z, width, height, depth)) continue;
|
||||
|
||||
var containerBox = container.BoundingBox;
|
||||
if (boxBoundingBox.Intersects(ref containerBox)) entityIds.Add(container.EntityId);
|
||||
}
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, BoundingBoxD boxD)
|
||||
{
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].GetExact(entityIds, boxD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.GetExact(entityIds, boxD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.Overlaps(x, y, z, width, height, depth)
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
public List<long> GetExact(List<long> entityIds, long flags, BoundingBoxD boxD)
|
||||
{
|
||||
if (flags == 0L) return GetExact(entityIds, boxD);
|
||||
var x = (float)boxD.Min.X;
|
||||
var y = (float)boxD.Min.Y;
|
||||
var z = (float)boxD.Min.Z;
|
||||
var width = (float)boxD.Max.X - x;
|
||||
var height = (float)boxD.Max.Y - y;
|
||||
var depth = (float)boxD.Max.Z - z;
|
||||
|
||||
if (!_bounds.Overlaps(x, y, z, width, height, depth)) return entityIds;
|
||||
|
||||
if (nodes[0] != null)
|
||||
{
|
||||
var index = IndexOf(x, y, z, width, height, depth);
|
||||
if (index != OUTSIDE) nodes[index].GetExact(entityIds, flags, boxD);
|
||||
else
|
||||
foreach (var ocTree in nodes)
|
||||
ocTree.GetExact(entityIds, flags, boxD);
|
||||
}
|
||||
|
||||
entityIds.AddRange(from container in _containers
|
||||
where container.MatchesFlags(flags) && container.Overlaps(x, y, z, width, height, depth)
|
||||
select container.EntityId);
|
||||
|
||||
return entityIds;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class Container : IPoolable
|
||||
{
|
||||
public long EntityId;
|
||||
public long Flags;
|
||||
public OcTree Parent;
|
||||
public float X { get; private set; }
|
||||
public float Y { get; private set; }
|
||||
public float Z { get; private set; }
|
||||
public float Width { get; private set; }
|
||||
public float Height { get; private set; }
|
||||
public float Depth { get; private set; }
|
||||
|
||||
public BoundingBoxD BoundingBox =>
|
||||
new BoundingBoxD(new Vector3(X, Y, Z), new Vector3(X + Width, Y + Height, Z + Depth));
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
EntityId = -1;
|
||||
Flags = 0;
|
||||
X = 0;
|
||||
Y = 0;
|
||||
Z = 0;
|
||||
Width = 0;
|
||||
Height = 0;
|
||||
Depth = 0;
|
||||
}
|
||||
|
||||
public Container Set(long entityId, long flags, float x, float y, float z, float width, float height,
|
||||
float depth)
|
||||
{
|
||||
EntityId = entityId;
|
||||
Flags = flags;
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Depth = depth;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Container Set(float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
Z = z;
|
||||
Width = width;
|
||||
Height = height;
|
||||
Depth = depth;
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool MatchesFlags(long flags)
|
||||
{
|
||||
return (Flags & flags) > 0;
|
||||
}
|
||||
|
||||
public bool Contains(float x, float y, float z)
|
||||
{
|
||||
return X <= x && x <= X + Width &&
|
||||
Y <= y && y <= Y + Height &&
|
||||
Z <= z && z <= Z + Depth;
|
||||
}
|
||||
|
||||
public bool Contains(float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
var xMax = x + width;
|
||||
var yMax = x + height;
|
||||
var zMax = x + depth;
|
||||
|
||||
return x > X && x < X + Width && xMax > X && xMax < X + Width &&
|
||||
y > Y && y < Y + Height && yMax > Y && yMax < Y + Height &&
|
||||
z > Z && z < Z + Depth && zMax > Z && zMax < Z + Depth;
|
||||
}
|
||||
|
||||
public bool Overlaps(float x, float y, float z, float width, float height, float depth)
|
||||
{
|
||||
if (X > x + width) return false;
|
||||
if (x > X + Width) return false;
|
||||
if (Y > y + height) return false;
|
||||
if (y > Y + Height) return false;
|
||||
if (Z > z + depth) return false;
|
||||
if (z > Z + Depth) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Container c)
|
||||
{
|
||||
return Contains(c.X, c.Y, c.Z, c.Width, c.Height, c.Depth);
|
||||
}
|
||||
}
|
||||
}
|
140
GlobalShared/OcTree/OcTreeHandler.cs
Normal file
140
GlobalShared/OcTree/OcTreeHandler.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Global.Patches;
|
||||
using Global.Shared.API;
|
||||
using Global.Shared.Events;
|
||||
using Global.Shared.OcTree.Data;
|
||||
using Global.Shared.Plugin;
|
||||
using Sandbox.Game.Entities;
|
||||
using VRage.Game.Components;
|
||||
using VRage.Game.Entity;
|
||||
using VRage.Game.ModAPI;
|
||||
|
||||
namespace Global.Shared.OcTree
|
||||
{
|
||||
public class OcTreeHandler
|
||||
{
|
||||
public GenericOcTree<GridData> GridTree { get; private set; }
|
||||
|
||||
public GenericOcTree<ControllableEntityData> CharacterTree { get; private set; }
|
||||
|
||||
internal void Init()
|
||||
{
|
||||
var cfg = GlobalInstance.Config;
|
||||
var offset = cfg.CenterPosition;
|
||||
var x = cfg.Size;
|
||||
var y = cfg.Size;
|
||||
var z = cfg.Size;
|
||||
GridTree = new GenericOcTree<GridData>(offset.X - x, offset.Y - y, offset.Z - z, x * 2, y * 2, z * 2,
|
||||
cfg.Capacity, cfg.MaxDepth);
|
||||
CharacterTree =
|
||||
new GenericOcTree<ControllableEntityData>(offset.X - x, offset.Y - y, offset.Z - z, x * 2, y * 2, z * 2,
|
||||
cfg.Capacity, cfg.MaxDepth);
|
||||
|
||||
BlockEvents.OnPlayerControlAcquired += AddControllableEntity;
|
||||
BlockEvents.OnPlayerControlReleased += RemoveControllableEntity;
|
||||
}
|
||||
|
||||
public IGridData AddGrid(MyCubeGrid grid)
|
||||
{
|
||||
var bigOwner = grid.BigOwners.Count == 0 ? "No Owner!" : grid.BigOwners.First().ToString();
|
||||
// check if grid is new subgrid
|
||||
if (MyMechanicalConnectionBlockBasePatch.IsCreatingSubPart)
|
||||
{
|
||||
GlobalInstance.Log.Trace(
|
||||
$"Grid is subgrid, ignoring {grid.DisplayName} {grid.PositionComp.GetPosition()} {bigOwner}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!GridTree.Contains(grid.EntityId))
|
||||
{
|
||||
var grids = grid.GetGridGroup(GridLinkTypeEnum.Mechanical).GetGrids(new List<IMyCubeGrid>());
|
||||
var largestGrid = grid;
|
||||
if (grids.Count > 1)
|
||||
foreach (var g in grids.Cast<MyCubeGrid>())
|
||||
if (g.BlocksCount > largestGrid.BlocksCount)
|
||||
largestGrid = g;
|
||||
|
||||
grid = largestGrid;
|
||||
//GlobalInstance.Log.Info(
|
||||
//$"Adding grid to OcTree: {grid.EntityId} {grid.DisplayName} {grid.PositionComp.GetPosition()}");
|
||||
|
||||
var data = new GridData(grid);
|
||||
grid.PositionComp.OnPositionChanged += Grid_OnPositionChanged;
|
||||
grid.OnMarkForClose += RemoveGrid;
|
||||
grid.OnTeleported += Grid_OnChanged;
|
||||
GridTree.Insert(data);
|
||||
grid.OnConnectionChanged += Grid_OnConnectionChanged;
|
||||
|
||||
GridEvents.GridCreated?.Invoke(grid);
|
||||
return data;
|
||||
}
|
||||
|
||||
GlobalInstance.Log.Debug(
|
||||
$"Attempted to add grid to OcTree that already exists, {grid.DisplayName} {grid.PositionComp.GetPosition()} {bigOwner}");
|
||||
|
||||
var existing = GridTree[grid.EntityId];
|
||||
|
||||
if (existing != null) return existing;
|
||||
|
||||
GridTree.Remove(grid.EntityId);
|
||||
return AddGrid(grid);
|
||||
}
|
||||
|
||||
public void AddControllableEntity(IMyControllableEntity entity)
|
||||
{
|
||||
if (entity.ControllerInfo?.Controller?.Player == null && !(entity is IMyCharacter))
|
||||
//GlobalInstance.Log.Info($"Entity is not controlled {entity.Entity.EntityId} {entity.Entity.DisplayName}");
|
||||
return;
|
||||
//GlobalInstance.Log.Debug($"AddControllableEntity {entity.Entity.EntityId} {entity.Entity.DisplayName}");
|
||||
entity.Entity.OnMarkForClose += RemoveControllableEntity;
|
||||
|
||||
var data = new ControllableEntityData(entity);
|
||||
|
||||
if (!CharacterTree.Contains(entity.Entity.EntityId)) CharacterTree.Insert(data);
|
||||
}
|
||||
|
||||
public void RemoveControllableEntity(IMyControllableEntity entity)
|
||||
{
|
||||
RemoveControllableEntity((MyEntity)entity);
|
||||
}
|
||||
|
||||
public void RemoveControllableEntity(MyEntity entity)
|
||||
{
|
||||
//GlobalInstance.Log.Debug("RemoveControllableEntity");
|
||||
CharacterTree.Remove(entity.EntityId);
|
||||
}
|
||||
|
||||
private void Grid_OnConnectionChanged(MyCubeGrid arg1, GridLinkTypeEnum arg2)
|
||||
{
|
||||
if (arg2 != GridLinkTypeEnum.Mechanical) return;
|
||||
|
||||
GridTree.Update(arg1.EntityId);
|
||||
}
|
||||
|
||||
internal void RemoveGrid(MyEntity grid)
|
||||
{
|
||||
GlobalInstance.Log.Debug($"Removing grid from OcTree: {grid.EntityId} {grid.DisplayName}");
|
||||
GridEvents.GridRemoved?.Invoke(grid as MyCubeGrid);
|
||||
|
||||
grid.PositionComp.OnPositionChanged -= Grid_OnPositionChanged;
|
||||
grid.OnMarkForClose -= RemoveGrid;
|
||||
grid.OnTeleported -= Grid_OnChanged;
|
||||
|
||||
GridTree.Remove(grid.EntityId);
|
||||
}
|
||||
|
||||
private void Grid_OnChanged(MyEntity obj)
|
||||
{
|
||||
GridTree.Update(obj.EntityId);
|
||||
}
|
||||
|
||||
private void Grid_OnPositionChanged(MyPositionComponentBase obj)
|
||||
{
|
||||
if (!(obj.Entity is MyCubeGrid grid)) return;
|
||||
if (grid.Closed || grid.MarkedForClose) return;
|
||||
|
||||
GridTree.Update(grid.EntityId);
|
||||
}
|
||||
}
|
||||
}
|
36
GlobalShared/OcTree/Pool.cs
Normal file
36
GlobalShared/OcTree/Pool.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Global.Shared.OcTree
|
||||
{
|
||||
public interface IPoolable
|
||||
{
|
||||
void Reset();
|
||||
}
|
||||
|
||||
public class Pool<E> where E : IPoolable
|
||||
{
|
||||
private readonly Func<E> creator;
|
||||
private readonly List<E> pool = new List<E>();
|
||||
|
||||
public Pool(Func<E> creator)
|
||||
{
|
||||
this.creator = creator;
|
||||
}
|
||||
|
||||
public E Obtain()
|
||||
{
|
||||
if (pool.Count <= 0) return creator();
|
||||
|
||||
var e = pool[0];
|
||||
pool.RemoveAt(0);
|
||||
return e;
|
||||
}
|
||||
|
||||
public void Release(E e)
|
||||
{
|
||||
e.Reset();
|
||||
pool.Add(e);
|
||||
}
|
||||
}
|
||||
}
|
66
GlobalShared/Patches/CubeGridPatch.cs
Normal file
66
GlobalShared/Patches/CubeGridPatch.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Threading;
|
||||
using Global.Shared.Events;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Definitions;
|
||||
using Sandbox.Game.Entities;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(MyCubeGrid))]
|
||||
public class CubeGridPatch
|
||||
{
|
||||
private static readonly ThreadLocal<int> CallDepth = new ThreadLocal<int>();
|
||||
|
||||
public static ThreadLocal<ulong> PlayerId = new ThreadLocal<ulong>();
|
||||
public static bool IsInMergeGridInternal => CallDepth.Value > 0;
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch("MergeGridInternal")]
|
||||
private static bool MergeGridInternalPrefix()
|
||||
{
|
||||
// if (Config.Patches.DisableAllPatches || !Config.Patches.EnableGridMerge) return true;
|
||||
|
||||
CallDepth.Value++;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch("MergeGridInternal")]
|
||||
private static void MergeGridInternalPostfix(MyCubeGrid __instance)
|
||||
{
|
||||
if (!IsInMergeGridInternal) return;
|
||||
if (--CallDepth.Value > 0) return;
|
||||
__instance.GridSystems.ConveyorSystem.FlagForRecomputation();
|
||||
}
|
||||
|
||||
[HarmonyPatch("CanPlaceBlock", typeof(Vector3I), typeof(Vector3I), typeof(MyBlockOrientation),
|
||||
typeof(MyCubeBlockDefinition), typeof(ulong), typeof(int?), typeof(bool), typeof(bool))]
|
||||
[HarmonyPostfix]
|
||||
private static void CanPlaceBlockPostfix(Vector3I min,
|
||||
Vector3I max,
|
||||
MyBlockOrientation orientation,
|
||||
MyCubeBlockDefinition definition,
|
||||
ulong placingPlayer = 0,
|
||||
int? ignoreMultiblockId = null,
|
||||
bool ignoreFracturedPieces = false,
|
||||
bool isProjection = false)
|
||||
{
|
||||
PlayerId.Value = placingPlayer;
|
||||
}
|
||||
|
||||
[HarmonyPatch("CanAddCubes", typeof(Vector3I), typeof(Vector3I), typeof(MyBlockOrientation),
|
||||
typeof(MyCubeBlockDefinition))]
|
||||
[HarmonyPostfix]
|
||||
private static bool CanAddCubesPostfix(bool canPlace, MyCubeGrid __instance, Vector3I min,
|
||||
Vector3I max,
|
||||
MyBlockOrientation? orientation,
|
||||
MyCubeBlockDefinition definition)
|
||||
{
|
||||
BlockEvents.OnCanPlaceBlockEvent(__instance, orientation, definition, PlayerId.Value, ref canPlace);
|
||||
PlayerId.Value = 0;
|
||||
return canPlace;
|
||||
}
|
||||
}
|
||||
}
|
85
GlobalShared/Patches/EntityNamePatch.cs
Normal file
85
GlobalShared/Patches/EntityNamePatch.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.Entities;
|
||||
using VRage.Game.Entity;
|
||||
|
||||
namespace Global.Shared.Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(MyEntities))]
|
||||
public class EntityNamePatch
|
||||
{
|
||||
public static ConcurrentDictionary<long, string> EntityNameReverseLookup =
|
||||
new ConcurrentDictionary<long, string>();
|
||||
// reverse dictionary
|
||||
|
||||
[HarmonyPatch("SetEntityName")]
|
||||
[HarmonyPrefix]
|
||||
private static bool SetEntityNamePrefix(MyEntity myEntity, bool possibleRename)
|
||||
{
|
||||
if (string.IsNullOrEmpty(myEntity.Name))
|
||||
return false;
|
||||
string previousName = null;
|
||||
if (possibleRename)
|
||||
if (EntityNameReverseLookup.ContainsKey(myEntity.EntityId))
|
||||
{
|
||||
previousName = EntityNameReverseLookup[myEntity.EntityId];
|
||||
if (previousName != myEntity.Name) MyEntities.m_entityNameDictionary.Remove(previousName);
|
||||
}
|
||||
|
||||
if (MyEntities.m_entityNameDictionary.TryGetValue(myEntity.Name, out var myEntity1))
|
||||
{
|
||||
if (myEntity1 == myEntity)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
MyEntities.m_entityNameDictionary[myEntity.Name] = myEntity;
|
||||
EntityNameReverseLookup[myEntity.EntityId] = myEntity.Name;
|
||||
}
|
||||
|
||||
// MyEntitiesButBetter.CallRename(myEntity, previousName, myEntity.Name);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch("RemoveName")]
|
||||
[HarmonyPrefix]
|
||||
private static bool RemoveNamePrefix(MyEntity entity)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entity.Name))
|
||||
return false;
|
||||
MyEntities.m_entityNameDictionary.Remove(entity.Name);
|
||||
EntityNameReverseLookup.Remove(entity.EntityId);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch("IsNameExists")]
|
||||
[HarmonyPrefix]
|
||||
private static bool IsNameExistsPrefix(ref bool __result, MyEntity entity, string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(entity.Name))
|
||||
{
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (MyEntities.m_entityNameDictionary.ContainsKey(name))
|
||||
{
|
||||
var ent = MyEntities.m_entityNameDictionary[entity.Name];
|
||||
__result = ent != entity;
|
||||
return false;
|
||||
}
|
||||
|
||||
__result = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch("UnloadData")]
|
||||
[HarmonyPostfix]
|
||||
private static void UnloadDataPostfix()
|
||||
{
|
||||
EntityNameReverseLookup.Clear();
|
||||
}
|
||||
}
|
||||
}
|
17
GlobalShared/Patches/MyConveyorLinePatch.cs
Normal file
17
GlobalShared/Patches/MyConveyorLinePatch.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Global.Shared.Patches;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.GameSystems.Conveyors;
|
||||
|
||||
namespace Global.Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(MyConveyorLine))]
|
||||
public class MyConveyorLinePatch
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(MyConveyorLine.UpdateIsWorking))]
|
||||
private static bool UpdateIsWorkingPrefix()
|
||||
{
|
||||
return !CubeGridPatch.IsInMergeGridInternal;
|
||||
}
|
||||
}
|
||||
}
|
55
GlobalShared/Patches/MyGamePruningStructurePatch.cs
Normal file
55
GlobalShared/Patches/MyGamePruningStructurePatch.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Global.Shared.API;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.Entities;
|
||||
using VRage.Game.Entity;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(MyGamePruningStructure))]
|
||||
public class MyGamePruningStructurePatch
|
||||
{
|
||||
private static readonly List<IGridData> _gridQueryStorage = new List<IGridData>();
|
||||
private static readonly List<IControllableEntityData> _queryStorage = new List<IControllableEntityData>();
|
||||
|
||||
// [HarmonyPatch("GetTopMostEntitiesInBox")]
|
||||
// [HarmonyPrefix]
|
||||
public static bool GetTopMostEntitiesInBox(
|
||||
ref BoundingBoxD box,
|
||||
List<MyEntity> result,
|
||||
MyEntityQueryType qtype = MyEntityQueryType.Both)
|
||||
{
|
||||
GetTopMostBox(ref box, result, qtype);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void GetTopMostBox(ref BoundingBoxD box, List<MyEntity> result, MyEntityQueryType qType)
|
||||
{
|
||||
_queryStorage.Clear();
|
||||
_gridQueryStorage.Clear();
|
||||
var gridSearch = GridFlag.None;
|
||||
if (qType.HasDynamic()) gridSearch |= GridFlag.DynamicGrid;
|
||||
if (qType.HasStatic()) gridSearch |= GridFlag.StaticGrid;
|
||||
if (gridSearch != GridFlag.None) GlobalAPI.LocateGrids(_gridQueryStorage, box, gridSearch);
|
||||
|
||||
result.AddRange(_gridQueryStorage.Select(e => e.CubeGrid));
|
||||
|
||||
if (qType.HasDynamic()) GlobalAPI.LocateCharacters(_queryStorage, box, PlayerFlag.Character);
|
||||
|
||||
result.AddRange(_queryStorage.Select(l => l.Entity.Entity));
|
||||
}
|
||||
|
||||
// [HarmonyPatch("GetAllTopMostStaticEntitiesInBox")]
|
||||
// [HarmonyPrefix]
|
||||
public static bool GetAllTopMostStaticEntitiesInBox(
|
||||
ref BoundingBoxD box,
|
||||
List<MyEntity> result,
|
||||
MyEntityQueryType qtype = MyEntityQueryType.Both)
|
||||
{
|
||||
GetTopMostBox(ref box, result, qtype);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
33
GlobalShared/Patches/MyMechanicalConnectionBlockBasePatch.cs
Normal file
33
GlobalShared/Patches/MyMechanicalConnectionBlockBasePatch.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Global.Shared.Plugin;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.Entities.Blocks;
|
||||
|
||||
namespace Global.Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(MyMechanicalConnectionBlockBase))]
|
||||
public class MyMechanicalConnectionBlockBasePatch
|
||||
{
|
||||
public static bool IsCreatingSubPart;
|
||||
|
||||
[HarmonyPatch("RaiseAttachedEntityChanged")]
|
||||
[HarmonyPrefix]
|
||||
public static void RaiseAttachedEntityChanged(MyMechanicalConnectionBlockBase __instance)
|
||||
{
|
||||
GlobalStatic.OcTreeHandler.GridTree.Get(__instance.CubeGrid.EntityId)?.RaiseAttachedEntityChanged();
|
||||
}
|
||||
|
||||
[HarmonyPatch("CreateTopPartAndAttach")]
|
||||
[HarmonyPrefix]
|
||||
public static void CreateTopPrefix(long builtBy, bool smallToLarge, bool instantBuild)
|
||||
{
|
||||
IsCreatingSubPart = true;
|
||||
}
|
||||
|
||||
[HarmonyPatch("CreateTopPartAndAttach")]
|
||||
[HarmonyPostfix]
|
||||
public static void CreateTopPostfix(long builtBy, bool smallToLarge, bool instantBuild)
|
||||
{
|
||||
IsCreatingSubPart = false;
|
||||
}
|
||||
}
|
||||
}
|
24
GlobalShared/Patches/MyTerminalBlockPatch.cs
Normal file
24
GlobalShared/Patches/MyTerminalBlockPatch.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.Entities.Cube;
|
||||
using VRage.Game;
|
||||
|
||||
namespace Global.Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(MyTerminalBlock))]
|
||||
public class MyTerminalBlockPatch
|
||||
{
|
||||
[HarmonyPatch("HasPlayerAccess")]
|
||||
[HarmonyPrefix]
|
||||
public static bool HasPlayerAccessPrefix(bool __result, long identityId,
|
||||
MyRelationsBetweenPlayerAndBlock defaultNoUser = MyRelationsBetweenPlayerAndBlock.NoOwnership)
|
||||
{
|
||||
if (identityId != 0) return true;
|
||||
|
||||
var defaultIsEnemyNeutralOrNoOwnership = defaultNoUser == MyRelationsBetweenPlayerAndBlock.Enemies ||
|
||||
defaultNoUser == MyRelationsBetweenPlayerAndBlock.Neutral ||
|
||||
defaultNoUser == MyRelationsBetweenPlayerAndBlock.NoOwnership;
|
||||
__result = !defaultIsEnemyNeutralOrNoOwnership;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
31
GlobalShared/Patches/TerminalSystemPatch.cs
Normal file
31
GlobalShared/Patches/TerminalSystemPatch.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Global.Shared.Util;
|
||||
using HarmonyLib;
|
||||
using Sandbox.Game.GameSystems;
|
||||
|
||||
namespace Global.Shared.Patches
|
||||
{
|
||||
[HarmonyPatch(typeof(MyGridTerminalSystem))]
|
||||
public class TerminalSystemPatch
|
||||
{
|
||||
public static readonly UintCache<long> OwnerCache = new UintCache<long>(337 * 60);
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(MyGridTerminalSystem.UpdateGridBlocksOwnership))]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool UpdateGridBlocksOwnershipPrefix(MyGridTerminalSystem __instance, long ownerID)
|
||||
{
|
||||
var key = __instance.GetHashCode() ^ ownerID;
|
||||
if (OwnerCache.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value == (uint)ownerID) return false;
|
||||
|
||||
OwnerCache.Forget(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
OwnerCache.Store(key, (uint)ownerID, 240 + ((uint)key & 63));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
27
GlobalShared/Patching/PatchHelpers.cs
Normal file
27
GlobalShared/Patching/PatchHelpers.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Global.Shared.Logging;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace Global.Shared.Patching
|
||||
{
|
||||
public static class PatchHelpers
|
||||
{
|
||||
public static bool PatchAll(IPluginLogger log, Harmony harmony)
|
||||
{
|
||||
log.Info("Applying patches...");
|
||||
|
||||
try
|
||||
{
|
||||
harmony.PatchAll(Assembly.GetCallingAssembly());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
log.Critical(ex, "Failed to apply patches!");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
24
GlobalShared/Plugin/GlobalInstance.cs
Normal file
24
GlobalShared/Plugin/GlobalInstance.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using Global.Shared.Config;
|
||||
using Global.Shared.Logging;
|
||||
|
||||
namespace Global.Shared.Plugin
|
||||
{
|
||||
public static class GlobalInstance
|
||||
{
|
||||
public static IPluginLogger Log => Plugin.Log;
|
||||
public static IGlobalPlugin Plugin { get; private set; }
|
||||
public static IPluginConfig Config => Plugin.Config;
|
||||
|
||||
public static void SetPlugin(IGlobalPlugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
GlobalStatic.OcTreeHandler.Init();
|
||||
}
|
||||
|
||||
public static void Run(Action action)
|
||||
{
|
||||
Plugin.Run(action);
|
||||
}
|
||||
}
|
||||
}
|
40
GlobalShared/Plugin/GlobalStatic.cs
Normal file
40
GlobalShared/Plugin/GlobalStatic.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Global.Shared.OcTree;
|
||||
using Global.Shared.Patches;
|
||||
using Sandbox.Game.Entities;
|
||||
using Sandbox.ModAPI;
|
||||
using VRage.ModAPI;
|
||||
|
||||
namespace Global.Shared.Plugin
|
||||
{
|
||||
public static class GlobalStatic
|
||||
{
|
||||
public static OcTreeHandler OcTreeHandler = new OcTreeHandler();
|
||||
|
||||
internal static void Init()
|
||||
{
|
||||
MyAPIGateway.Entities.OnEntityAdd += Entities_OnEntityAdd;
|
||||
var set = new HashSet<IMyEntity>();
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
MyAPIGateway.Entities.GetEntities(set);
|
||||
foreach (var myEntity in set) Entities_OnEntityAdd(myEntity);
|
||||
stopwatch.Stop();
|
||||
GlobalInstance.Log.Info($"Collected all entities from startup. Took {stopwatch.ElapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
internal static void Update()
|
||||
{
|
||||
TerminalSystemPatch.OwnerCache.Cleanup();
|
||||
}
|
||||
|
||||
private static void Entities_OnEntityAdd(IMyEntity obj)
|
||||
{
|
||||
if (obj is MyCubeGrid grid)
|
||||
OcTreeHandler.AddGrid(grid);
|
||||
else if (obj is IMyControllableEntity controllableEntity)
|
||||
OcTreeHandler.AddControllableEntity(controllableEntity);
|
||||
}
|
||||
}
|
||||
}
|
15
GlobalShared/Plugin/IGlobalPlugin.cs
Normal file
15
GlobalShared/Plugin/IGlobalPlugin.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using Global.Shared.Config;
|
||||
using Global.Shared.Logging;
|
||||
|
||||
namespace Global.Shared.Plugin
|
||||
{
|
||||
public interface IGlobalPlugin
|
||||
{
|
||||
IPluginLogger Log { get; }
|
||||
IPluginConfig Config { get; }
|
||||
long Tick { get; }
|
||||
bool Started { get; }
|
||||
void Run(Action action);
|
||||
}
|
||||
}
|
35
GlobalShared/Properties/AssemblyInfo.cs
Normal file
35
GlobalShared/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("GlobalShared")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("GlobalShared")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2022")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("C04593FB-B399-43C1-83C5-E1262A3E0465")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
19
GlobalShared/Util/KeenExtensions.cs
Normal file
19
GlobalShared/Util/KeenExtensions.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Global.Shared.OcTree;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.Util
|
||||
{
|
||||
public static class KeenExtensions
|
||||
{
|
||||
public static BoundingBoxD SetToContainer(this BoundingBoxD box, IContainer container)
|
||||
{
|
||||
box.Min.X = container.X;
|
||||
box.Min.Y = container.Y;
|
||||
box.Min.Z = container.Z;
|
||||
box.Max.X = container.X + container.Width;
|
||||
box.Max.Y = container.Y + container.Height;
|
||||
box.Max.Z = container.Z + container.Depth;
|
||||
return box;
|
||||
}
|
||||
}
|
||||
}
|
42
GlobalShared/Util/NexusUtils.cs
Normal file
42
GlobalShared/Util/NexusUtils.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
using Nexus.API;
|
||||
using Nexus.Utilities;
|
||||
using Sandbox.Engine.Multiplayer;
|
||||
using Sandbox.ModAPI;
|
||||
using ServerNetwork.Sync;
|
||||
using VRageMath;
|
||||
|
||||
namespace Global.Shared.Util
|
||||
{
|
||||
public class NexusUtils
|
||||
{
|
||||
public static NexusServerSideAPI nexusServerAPI = new NexusServerSideAPI(1234);
|
||||
public static NexusAPI nexusAPI = new NexusAPI((ushort)nexusServerAPI.CrossServerModID);
|
||||
|
||||
|
||||
public void BroadCastMessage(string author, string message, Color color)
|
||||
{
|
||||
var scriptedChatMsg1 = new ScriptedChatMsg
|
||||
{
|
||||
Author = author,
|
||||
Text = message,
|
||||
Font = "White",
|
||||
Color = color,
|
||||
Target = 0
|
||||
};
|
||||
|
||||
var msg = new ScriptedMessage
|
||||
{
|
||||
Author = scriptedChatMsg1.Author,
|
||||
ChatColor = scriptedChatMsg1.Color.GetDrawingColor().ToArgb(),
|
||||
Target = scriptedChatMsg1.Target,
|
||||
Text = scriptedChatMsg1.Text
|
||||
};
|
||||
NexusServerSideAPI.SendMessageToAllServers(ref nexusAPI,
|
||||
MyAPIGateway.Utilities.SerializeToBinary(msg));
|
||||
}
|
||||
}
|
||||
}
|
93
GlobalShared/Util/UintCache.cs
Normal file
93
GlobalShared/Util/UintCache.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Global.Shared.Plugin;
|
||||
|
||||
namespace Global.Shared.Util
|
||||
{
|
||||
public class UintCache<TK>
|
||||
{
|
||||
private readonly ConcurrentDictionary<TK, ulong> cache = new ConcurrentDictionary<TK, ulong>();
|
||||
private readonly ulong cleanupPeriod;
|
||||
private readonly TK[] keysToDelete;
|
||||
private readonly uint maxDeleteCount;
|
||||
private ulong nextCleanup;
|
||||
private ulong tick;
|
||||
|
||||
public UintCache(uint cleanupPeriod, uint maxDeleteCount = 64)
|
||||
{
|
||||
this.cleanupPeriod = (ulong)cleanupPeriod << 32;
|
||||
this.maxDeleteCount = maxDeleteCount;
|
||||
|
||||
keysToDelete = new TK[this.maxDeleteCount];
|
||||
nextCleanup = tick + cleanupPeriod;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Cleanup()
|
||||
{
|
||||
if (GlobalInstance.Plugin == null) return;
|
||||
if ((tick = (ulong)GlobalInstance.Plugin.Tick << 32) < nextCleanup)
|
||||
return;
|
||||
|
||||
nextCleanup = tick + cleanupPeriod;
|
||||
|
||||
var count = 0u;
|
||||
foreach (var (key, item) in cache)
|
||||
{
|
||||
if (item >= tick)
|
||||
continue;
|
||||
|
||||
keysToDelete[count++] = key;
|
||||
if (count == maxDeleteCount)
|
||||
break;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
cache.Remove(keysToDelete[i]);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Clear()
|
||||
{
|
||||
cache.Clear();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Store(TK key, uint value, uint lifetime)
|
||||
{
|
||||
var expires = tick + ((ulong)lifetime << 32);
|
||||
cache[key] = expires | value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Extend(TK key, uint lifetime)
|
||||
{
|
||||
if (cache.TryGetValue(key, out var item))
|
||||
cache[key] = (tick + ((ulong)lifetime << 32)) | (item & 0xfffffffful);
|
||||
}
|
||||
|
||||
public void Forget(TK key)
|
||||
{
|
||||
cache.Remove(key);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(TK key, out uint value)
|
||||
{
|
||||
if (cache.TryGetValue(key, out var item))
|
||||
if (item >= tick)
|
||||
{
|
||||
value = (uint)item;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = 0u;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user