using System;
using System.Collections.Generic;
namespace UnityEngine.PathTracing.Core
{
internal class TextureQuadTree
{
public class TextureNode
{
public TextureNode TopLeft;
public TextureNode TopRight;
public TextureNode BottomLeft;
public TextureNode BottomRight;
public TextureNode Parent;
public int PosX;
public int PosY;
public int Size;
}
private readonly int _size;
private readonly List<TextureNode> _leaves;
public TextureQuadTree(int size)
{
_size = size;
var root = new TextureNode() { PosX = 0, PosY = 0, Size = _size };
_leaves = new List<TextureNode>() { root };
}
private void SubdivideNode(TextureNode node)
{
node.TopLeft = new TextureNode()
{
PosX = node.PosX,
PosY = node.PosY,
Size = node.Size / 2,
Parent = node
};
node.BottomLeft = new TextureNode()
{
PosX = node.PosX,
PosY = node.PosY + node.Size / 2,
Size = node.Size / 2,
Parent = node
};
node.TopRight = new TextureNode()
{
PosX = node.PosX + node.Size / 2,
PosY = node.PosY,
Size = node.Size / 2,
Parent = node
};
node.BottomRight = new TextureNode()
{
PosX = node.PosX + node.Size / 2,
PosY = node.PosY + node.Size / 2,
Size = node.Size / 2,
Parent = node
};
}
public bool AddTexture(int size, out TextureNode node)
{
int targetSize = Mathf.Min(Mathf.NextPowerOfTwo(size), _size);
// Search for a node with the right size
int bestSize = int.MaxValue;
TextureNode candidate = null;
for (int i = _leaves.Count - 1; i >= 0; i--)
{
// If we have a match, return it
int leafSize = _leaves[i].Size;
if (targetSize == leafSize)
{
node = _leaves[i];
_leaves.RemoveAt(i);
return true;
}
// Keep track of the best candidate we find
if (targetSize < leafSize && leafSize < bestSize)
{
candidate = _leaves[i];
bestSize = leafSize;
}
}
// If we have a candidate, subdivide one child until we are at the right size
if (candidate != null)
{
while (candidate.Size != targetSize)
{
SubdivideNode(candidate);
_leaves.Remove(candidate);
_leaves.Add(candidate.BottomRight);
_leaves.Add(candidate.BottomLeft);
_leaves.Add(candidate.TopRight);
candidate = candidate.TopLeft;
}
node = candidate;
return true;
}
node = null;
return false;
}
public void RemoveTexture(TextureNode node)
{
bool ShouldCollapse(TextureNode node)
{
return
(node.TopLeft == null || _leaves.Contains(node.TopLeft)) &&
(node.TopRight == null || _leaves.Contains(node.TopRight)) &&
(node.BottomLeft == null || _leaves.Contains(node.BottomLeft)) &&
(node.BottomRight == null || _leaves.Contains(node.BottomRight));
}
// Add the node to the list of leaves
_leaves.Add(node);
// Recursively collapse the quad tree if the parent node is empty, to avoid fragmentation
TextureNode parent = node.Parent;
while (parent != null && ShouldCollapse(parent))
{
// Remove the children from the leaves list
if (_leaves.Contains(parent.TopLeft)) _leaves.Remove(parent.TopLeft);
if (_leaves.Contains(parent.TopRight)) _leaves.Remove(parent.TopRight);
if (_leaves.Contains(parent.BottomLeft)) _leaves.Remove(parent.BottomLeft);
if (_leaves.Contains(parent.BottomRight)) _leaves.Remove(parent.BottomRight);
// Collapse the parent node
parent.TopLeft = null;
parent.TopRight = null;
parent.BottomLeft = null;
parent.BottomRight = null;
// Add the parent to the leaves list, and check the next parent
_leaves.Add(parent);
parent = parent.Parent;
}
}
public bool HasSpaceForTexture(int textureSize)
{
return _leaves.Exists(leaf => leaf.Size >= textureSize);
}
public bool IsFull => _leaves.Count == 0;
}
}