Skip to content

Commit

Permalink
Merge pull request #16 from Jnick-24/main
Browse files Browse the repository at this point in the history
Error handling in OnBlockRemoved.
  • Loading branch information
InvalidArgument3 authored May 12, 2024
2 parents 0a5fd3b + 73e0c04 commit df7f88f
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 50 deletions.
94 changes: 65 additions & 29 deletions BlockCulling/Data/Scripts/BlockCulling/BlockCulling_Helpers.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Definitions;
using Sandbox.ModAPI;
using VRage.Game.ModAPI;
using VRageMath;
Expand All @@ -11,17 +9,16 @@ namespace Scripts.BlockCulling
{
partial class BlockCulling
{
// TODO: Check for full mountpoints OR invisibility on neighboring blocks for culling.

private void SetTransparency(IMySlimBlock slimBlock, bool recursive = true)
private void SetTransparency(IMySlimBlock slimBlock, bool recursive = true, bool deepRecurse = true)
{
IMyCubeBlock block = slimBlock.FatBlock;
if (!recursive && block == null) // Don't do a non-recursive scan on slimblocks
return; // No logic would run so just return early

var blockSlimNeighbors = new List<IMySlimBlock>();
bool shouldCullBlock = BlockEligibleForCulling(slimBlock, blockSlimNeighbors);
bool shouldCullBlock = BlockEligibleForCulling(slimBlock, ref blockSlimNeighbors);

// Add to cache for making blocks invisible when inside grid's WorldAABB
if (block != null) // Only set fatblock visiblity to false
{
if (!_unCulledGrids.Contains(block.CubeGrid)) // Only set blocks to be invisible if grid is being culled
Expand All @@ -34,28 +31,67 @@ private void SetTransparency(IMySlimBlock slimBlock, bool recursive = true)
}

if (recursive) // Do set nearby blocks visibility to false.
foreach (var slimBlockN in blockSlimNeighbors.Where(slimBlockN => slimBlockN.FatBlock != null))
SetTransparency(slimBlockN, false);
foreach (var slimBlockN in blockSlimNeighbors)
SetTransparency(slimBlockN, deepRecurse, false);
}

private bool BlockEligibleForCulling(IMySlimBlock slimBlock, List<IMySlimBlock> blockSlimNeighbors = null)
private bool BlockEligibleForCulling(IMySlimBlock slimBlock, ref List<IMySlimBlock> blockSlimNeighbors)
{
if (blockSlimNeighbors == null)
blockSlimNeighbors = new List<IMySlimBlock>();
foreach (var blockPos in GetSurfacePositions(slimBlock))
{
IMySlimBlock neighbor = slimBlock.CubeGrid.GetCubeBlock(blockPos);
if (neighbor != null)
blockSlimNeighbors.Add(neighbor);
}

IMyCubeBlock block = slimBlock?.FatBlock;
if (block == null) return false;

if (blockSlimNeighbors == null)
blockSlimNeighbors = new List<IMySlimBlock>();
List<IMySlimBlock> slimNeighborsContributor = new List<IMySlimBlock>();

foreach (var blockPos in GetSurfacePositions(slimBlock))
foreach (var slimNeighbor in blockSlimNeighbors)
{
IMySlimBlock slimNeighbor = slimBlock.CubeGrid.GetCubeBlock(blockPos);
if (slimNeighbor == null)
continue;
// Determine if block is surrounded on all sides
int surroundingBlockCount = 0;
foreach (Vector3I surfacePosition in GetSurfacePositions(slimNeighbor))
if (slimNeighbor.CubeGrid.CubeExists(surfacePosition))
surroundingBlockCount++;

// If block is exposed and block doesn't fully occlude this block, skip.
if (surroundingBlockCount != GetBlockFaceCount(slimNeighbor) && !ConnectsWithFullMountPoint(slimBlock, slimNeighbor)) // If any neighbor block is exposed, check if this block is completely occluded.
return false;

if (slimNeighbor.FatBlock == null || !(slimNeighbor.FatBlock is IMyLightingBlock || slimNeighbor.BlockDefinition.Id.SubtypeName.Contains("Window"))) // Limit to slimblocks and fatblocks with physics (and not windows)
blockSlimNeighbors.Add(slimNeighbor);
slimNeighborsContributor.Add(slimNeighbor);
}

return slimNeighborsContributor.Count == GetBlockFaceCount(block);
}

private bool ConnectsWithFullMountPoint(IMySlimBlock thisBlock, IMySlimBlock slimNeighbor)
{
Quaternion slimNeighborRotation;
slimNeighbor.Orientation.GetQuaternion(out slimNeighborRotation);

foreach (var mountPoint in ((MyCubeBlockDefinition)slimNeighbor.BlockDefinition).MountPoints)
{
if (!Vector3I.BoxContains(thisBlock.Min, thisBlock.Max, (Vector3I)(slimNeighborRotation * mountPoint.Normal + slimNeighbor.Position)))
continue;

Vector3I mountSize = Vector3I.Abs(Vector3I.Round(mountPoint.End - mountPoint.Start));
if (mountSize.X + mountSize.Y + mountSize.Z == 2)
return true;
}

return blockSlimNeighbors.Count == GetBlockFaceCount(block);
return false;
}

private int GetBlockFaceCount(IMySlimBlock block)
{
Vector3I blockSize = Vector3I.Abs(block.Max - block.Min) + Vector3I.One;
return 2 * (blockSize.X * blockSize.Y + blockSize.Y * blockSize.Z + blockSize.Z * blockSize.X);
}

private int GetBlockFaceCount(IMyCubeBlock block)
Expand All @@ -64,11 +100,16 @@ private int GetBlockFaceCount(IMyCubeBlock block)
return 2 * (blockSize.X * blockSize.Y + blockSize.Y * blockSize.Z + blockSize.Z * blockSize.X);
}

private Vector3I[] _surfacePositions = new Vector3I[6];
private Vector3I[] GetSurfacePositions(IMySlimBlock block)
{
List<Vector3I> surfacePositions = new List<Vector3I>();
Vector3I blockSize = Vector3I.Abs(block.Max - block.Min) + Vector3I.One;
Vector3I min = block.Min;

int faceCount = 2 * (blockSize.X * blockSize.Y + blockSize.Y * blockSize.Z + blockSize.Z * blockSize.X);
if (_surfacePositions.Length != faceCount)
_surfacePositions = new Vector3I[faceCount];

int idx = 0;

for (int x = -1; x <= blockSize.X; x++)
{
Expand All @@ -80,18 +121,13 @@ private Vector3I[] GetSurfacePositions(IMySlimBlock block)
bool xLimit = (x == -1 || x == blockSize.X);
bool yLimit = (y == -1 || y == blockSize.Y);
bool zLimit = (z == -1 || z == blockSize.Z);
if (TernaryXor(xLimit, yLimit, zLimit)) // Avoid checking positions inside the block.
surfacePositions.Add(min + new Vector3I(x, y, z));
if ((!xLimit && yLimit ^ zLimit) || (xLimit && !(yLimit || zLimit))) // Avoid checking positions inside the block.
_surfacePositions[idx++] = block.Min + new Vector3I(x, y, z);
}
}
}

return surfacePositions.ToArray();
}

public static bool TernaryXor(bool a, bool b, bool c)
{
return (!a && (b ^ c)) || (a && !(b || c));
return _surfacePositions;
}
}
}
55 changes: 36 additions & 19 deletions BlockCulling/Data/Scripts/BlockCulling/BlockCulling_Session.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Sandbox.Game.Entities.Cube;
using Sandbox.ModAPI;
using VRage.Game.Components;
using VRage.Game.ModAPI;
using VRage.ModAPI;
using VRage.Utils;
using VRageMath;

namespace Scripts.BlockCulling
Expand All @@ -20,10 +17,17 @@ public partial class BlockCulling : MySessionComponentBase
/// </summary>
private readonly Dictionary<IMyCubeGrid, HashSet<IMyCubeBlock>> _culledBlocks = new Dictionary<IMyCubeGrid, HashSet<IMyCubeBlock>>();
/// <summary>
/// Map of all temporarily unculled grids, set by distance.
/// List of all temporarily un-culled grids, set by distance.
/// </summary>
private readonly HashSet<IMyCubeGrid> _unCulledGrids = new HashSet<IMyCubeGrid>();

/// <summary>
/// Making blocks invisible is expensive; this spreads it over several ticks.
/// </summary>
private readonly List<IMyCubeBlock> _queuedBlockCulls = new List<IMyCubeBlock>();

private const int MaxBlocksCulledPerTick = 100;

#region Base Methods

public override void LoadData()
Expand All @@ -43,24 +47,36 @@ protected override void UnloadData()
}

private int _drawTicks;
private Vector3D _cameraPosition;
public override void Draw()
{
// Making blocks invisible is expensive; this spreads it over several ticks.
if (_queuedBlockCulls.Count > 0)
{
int i = 0;
for (; i < _queuedBlockCulls.Count && i < MaxBlocksCulledPerTick; i++)
{
_queuedBlockCulls[i].Visible = false;
}
_queuedBlockCulls.RemoveRange(0, i);
}

_drawTicks++;
if (MyAPIGateway.Utilities.IsDedicated || _drawTicks % 15 != 0) // Only check every 1/4 second
if (MyAPIGateway.Utilities.IsDedicated || _drawTicks < 13) // Only check every 1/4 second
return;
_drawTicks = 0;

Vector3D cameraPosition = MyAPIGateway.Session?.Camera?.Position ?? Vector3D.MaxValue;
_cameraPosition = MyAPIGateway.Session?.Camera?.Position ?? Vector3D.MaxValue;
foreach (var grid in _culledBlocks.Keys)
{
// Recull blocks if within grid's WorldAABB
if (grid.WorldAABB.Contains(cameraPosition) != ContainmentType.Contains)
// Re-cull blocks if within grid's WorldAABB
if (grid.WorldAABB.Contains(_cameraPosition) != ContainmentType.Contains)
{
if (_unCulledGrids.Remove(grid))
foreach (var block in _culledBlocks[grid])
block.Visible = false;
_queuedBlockCulls.AddRange(_culledBlocks[grid]);
continue;
}

// Set blocks to visible if not already done
if (!_unCulledGrids.Add(grid))
continue;
Expand All @@ -81,11 +97,7 @@ private void OnEntityAdd(IMyEntity entity)

grid.OnBlockAdded += OnBlockPlace;
grid.OnBlockRemoved += OnBlockRemove;
grid.OnClose += gridEntity =>
{
_culledBlocks.Remove(grid);
_unCulledGrids.Remove(grid);
};
grid.OnClose += OnGridRemove;

_culledBlocks.Add(grid, new HashSet<IMyCubeBlock>());

Expand All @@ -105,12 +117,17 @@ private void OnBlockRemove(IMySlimBlock slimBlock)
IMySlimBlock slimNeighbor = slimBlock.CubeGrid.GetCubeBlock(blockPos);
if (slimNeighbor?.FatBlock != null)
{
slimNeighbor.FatBlock.Visible = true;
_culledBlocks[slimNeighbor.CubeGrid].Remove(slimNeighbor.FatBlock);
SetTransparency(slimNeighbor, true, false);
}
}
}

private void OnGridRemove(IMyEntity gridEntity)
{
_culledBlocks.Remove((IMyCubeGrid) gridEntity);
_unCulledGrids.Remove((IMyCubeGrid) gridEntity);
}

#endregion
}
}
4 changes: 2 additions & 2 deletions packages.config
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Mal.Mdk2.ModAnalyzers" version="2.1.8" targetFramework="net40" developmentDependency="true" />
<package id="Mal.Mdk2.References" version="2.1.9" targetFramework="net40" />
<package id="Mal.Mdk2.ModAnalyzers" version="2.1.8" targetFramework="net40" developmentDependency="true" requireReinstallation="true" />
<package id="Mal.Mdk2.References" version="2.1.9" targetFramework="net40" requireReinstallation="true" />
</packages>

0 comments on commit df7f88f

Please sign in to comment.