366 lines
9.8 KiB
C#
366 lines
9.8 KiB
C#
using System.Collections.Generic;
|
|
|
|
using UnityEngine;
|
|
|
|
namespace XericUI.XTable.Core
|
|
{
|
|
/// <summary>
|
|
/// 表格四叉树构建用的单元格元数据。
|
|
/// </summary>
|
|
public struct XTableQuadtreeCell
|
|
{
|
|
public int Row;
|
|
public int Col;
|
|
public string Namespace;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 四叉树查询结果——一个均匀象限的可见部分。
|
|
/// </summary>
|
|
public struct QuadrantResult
|
|
{
|
|
/// <summary>该象限在表格像素空间中的区域(左上角坐标系,y 向下为正)</summary>
|
|
public Rect Region;
|
|
|
|
/// <summary>统一命名空间(空字符串 = 空区域)</summary>
|
|
public string Namespace;
|
|
|
|
/// <summary>该象限包含的单元格坐标列表</summary>
|
|
public List<(int row, int col)> Cells;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 表格四叉树——基于单元格 <see cref="XTableQuadtreeCell.Namespace"/> 进行递归区域分割。
|
|
/// 若一个象限内存在不同命名空间的单元格,则继续分裂,直到所有差异分开。
|
|
/// 渲染阶段通过 <see cref="QueryVisible"/> 获取可见范围内的所有均匀象限。
|
|
/// </summary>
|
|
public class XTableQuadtree
|
|
{
|
|
#region 内部节点
|
|
|
|
private class Node
|
|
{
|
|
/// <summary>当前象限在表格像素空间中的区域</summary>
|
|
public Rect Region;
|
|
|
|
/// <summary>
|
|
/// null = 非均匀节点(有子节点),非 null = 均匀叶子节点。
|
|
/// 空字符串 "" 表示该区域无任何单元格。
|
|
/// </summary>
|
|
public string UniformNamespace;
|
|
|
|
/// <summary>子象限(null 表示叶子节点)</summary>
|
|
public Node[] Children;
|
|
|
|
/// <summary>该均匀象限包含的单元格坐标(仅叶子均匀节点有效)</summary>
|
|
public List<(int, int)> Cells;
|
|
|
|
public bool IsUniform => UniformNamespace != null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 字段
|
|
|
|
private Node m_Root;
|
|
private float[] m_RowHeights;
|
|
private float[] m_ColWidths;
|
|
private int m_MaxCellsPerNode;
|
|
|
|
/// <summary>四叉树是否已构建完成</summary>
|
|
public bool IsBuilt { get; private set; }
|
|
|
|
#endregion
|
|
|
|
#region 常量
|
|
|
|
#endregion
|
|
|
|
#region 构建
|
|
|
|
/// <summary>
|
|
/// 全量构建四叉树。
|
|
/// </summary>
|
|
/// <param name="totalWidth">表格总宽度(像素)</param>
|
|
/// <param name="totalHeight">表格总高度(像素)</param>
|
|
/// <param name="rowHeights">行高数组(索引 = 行号)</param>
|
|
/// <param name="colWidths">列宽数组(索引 = 列号)</param>
|
|
/// <param name="cells">所有有数据的单元格元数据列表</param>
|
|
public void Build(float totalWidth, float totalHeight,
|
|
float[] rowHeights, float[] colWidths,
|
|
List<XTableQuadtreeCell> cells, int maxCellsPerNode = 64)
|
|
{
|
|
if (rowHeights == null || colWidths == null || cells == null)
|
|
{
|
|
IsBuilt = false;
|
|
return;
|
|
}
|
|
|
|
m_RowHeights = rowHeights;
|
|
m_ColWidths = colWidths;
|
|
m_MaxCellsPerNode = maxCellsPerNode;
|
|
m_Root = new Node { Region = new Rect(0, 0, totalWidth, totalHeight) };
|
|
|
|
int maxRow = rowHeights.Length - 1;
|
|
int maxCol = colWidths.Length - 1;
|
|
|
|
if (maxRow >= 0 && maxCol >= 0)
|
|
BuildNode(m_Root, cells, 0, maxRow, 0, maxCol);
|
|
else
|
|
m_Root.UniformNamespace = "";
|
|
|
|
IsBuilt = true;
|
|
}
|
|
|
|
/// <summary>强制重建(外部入口,通常由表格组件在数据/布局变更后调用)</summary>
|
|
public void ForceRebuild(float totalWidth, float totalHeight,
|
|
float[] rowHeights, float[] colWidths,
|
|
List<XTableQuadtreeCell> cells, int maxCellsPerNode = 64)
|
|
{
|
|
Clear();
|
|
Build(totalWidth, totalHeight, rowHeights, colWidths, cells, maxCellsPerNode);
|
|
}
|
|
|
|
private void BuildNode(Node node, List<XTableQuadtreeCell> allCells,
|
|
int minRow, int maxRow, int minCol, int maxCol)
|
|
{
|
|
// 收集当前行列范围内的单元格
|
|
var cellsInRange = new List<XTableQuadtreeCell>();
|
|
for (int i = 0; i < allCells.Count; i++)
|
|
{
|
|
var cell = allCells[i];
|
|
if (cell.Row >= minRow && cell.Row <= maxRow &&
|
|
cell.Col >= minCol && cell.Col <= maxCol)
|
|
cellsInRange.Add(cell);
|
|
}
|
|
|
|
// 空区域
|
|
if (cellsInRange.Count == 0)
|
|
{
|
|
node.UniformNamespace = "";
|
|
return;
|
|
}
|
|
|
|
// 检查是否所有单元格命名空间一致
|
|
string firstNs = NormalizeNs(cellsInRange[0].Namespace);
|
|
bool allSame = true;
|
|
for (int i = 1; i < cellsInRange.Count; i++)
|
|
{
|
|
if (NormalizeNs(cellsInRange[i].Namespace) != firstNs)
|
|
{
|
|
allSame = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (allSame)
|
|
{
|
|
// 命名空间统一,但若空间跨度过大则继续分裂以便按块跳过不可见区域
|
|
if (cellsInRange.Count > m_MaxCellsPerNode &&
|
|
(minRow < maxRow || minCol < maxCol))
|
|
{
|
|
// 继续执行分裂逻辑(不要 return)
|
|
}
|
|
else
|
|
{
|
|
node.UniformNamespace = firstNs;
|
|
node.Cells = new List<(int, int)>(cellsInRange.Count);
|
|
for (int i = 0; i < cellsInRange.Count; i++)
|
|
node.Cells.Add((cellsInRange[i].Row, cellsInRange[i].Col));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 无法继续分裂(单行单列),强制标记为均匀
|
|
if (minRow >= maxRow && minCol >= maxCol)
|
|
{
|
|
node.UniformNamespace = firstNs;
|
|
node.Cells = new List<(int, int)>(cellsInRange.Count);
|
|
for (int i = 0; i < cellsInRange.Count; i++)
|
|
node.Cells.Add((cellsInRange[i].Row, cellsInRange[i].Col));
|
|
return;
|
|
}
|
|
|
|
// 分裂:在行列索引空间取中点
|
|
int midRow = (minRow + maxRow) / 2;
|
|
int midCol = (minCol + maxCol) / 2;
|
|
|
|
node.Children = new Node[4];
|
|
|
|
// 左上 (minRow..midRow, minCol..midCol)
|
|
node.Children[0] = MakeChild(minRow, midRow, minCol, midCol);
|
|
BuildNode(node.Children[0], allCells, minRow, midRow, minCol, midCol);
|
|
|
|
// 右上 (minRow..midRow, midCol+1..maxCol)
|
|
node.Children[1] = MakeChild(minRow, midRow, midCol + 1, maxCol);
|
|
BuildNode(node.Children[1], allCells, minRow, midRow, midCol + 1, maxCol);
|
|
|
|
// 左下 (midRow+1..maxRow, minCol..midCol)
|
|
node.Children[2] = MakeChild(midRow + 1, maxRow, minCol, midCol);
|
|
BuildNode(node.Children[2], allCells, midRow + 1, maxRow, minCol, midCol);
|
|
|
|
// 右下 (midRow+1..maxRow, midCol+1..maxCol)
|
|
node.Children[3] = MakeChild(midRow + 1, maxRow, midCol + 1, maxCol);
|
|
BuildNode(node.Children[3], allCells, midRow + 1, maxRow, midCol + 1, maxCol);
|
|
}
|
|
|
|
private Node MakeChild(int minRow, int maxRow, int minCol, int maxCol)
|
|
{
|
|
float x = GetColLeftX(minCol);
|
|
float y = GetRowTopY(minRow);
|
|
float w = GetColRightX(maxCol) - x;
|
|
float h = GetRowBottomY(maxRow) - y;
|
|
return new Node { Region = new Rect(x, y, w, h) };
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 查询
|
|
|
|
/// <summary>
|
|
/// 查询可见矩形内的所有均匀象限。
|
|
/// </summary>
|
|
/// <param name="visibleRect">可见矩形(左上角坐标系)</param>
|
|
/// <param name="results">结果列表(复用外部容器)</param>
|
|
public void QueryVisible(Rect visibleRect, List<QuadrantResult> results)
|
|
{
|
|
if (results == null)
|
|
results = new List<QuadrantResult>();
|
|
if (m_Root == null)
|
|
return;
|
|
|
|
QueryNode(m_Root, visibleRect, results);
|
|
}
|
|
|
|
/// <summary>获取完整表格的所有均匀叶子,用于背景分区提交。</summary>
|
|
public void QueryAll(List<QuadrantResult> results)
|
|
{
|
|
if (results == null || m_Root == null) return;
|
|
QueryAllNodes(m_Root, results);
|
|
}
|
|
|
|
private void QueryAllNodes(Node node, List<QuadrantResult> results)
|
|
{
|
|
if (node.IsUniform)
|
|
{
|
|
results.Add(new QuadrantResult { Region = node.Region, Namespace = node.UniformNamespace, Cells = node.Cells });
|
|
return;
|
|
}
|
|
if (node.Children == null) return;
|
|
for (int i = 0; i < node.Children.Length; i++)
|
|
if (node.Children[i] != null) QueryAllNodes(node.Children[i], results);
|
|
}
|
|
|
|
private void QueryNode(Node node, Rect visibleRect, List<QuadrantResult> results)
|
|
{
|
|
// 不相交则跳过
|
|
if (!RectIntersects(node.Region, visibleRect))
|
|
return;
|
|
|
|
if (node.IsUniform)
|
|
{
|
|
// 裁剪到可见范围
|
|
Rect clipped = RectIntersection(node.Region, visibleRect);
|
|
if (clipped.width > 0 && clipped.height > 0)
|
|
{
|
|
results.Add(new QuadrantResult
|
|
{
|
|
Region = clipped,
|
|
Namespace = node.UniformNamespace,
|
|
Cells = node.Cells
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 非均匀节点:递归查询子节点
|
|
if (node.Children != null)
|
|
{
|
|
for (int i = 0; i < node.Children.Length; i++)
|
|
{
|
|
if (node.Children[i] != null)
|
|
QueryNode(node.Children[i], visibleRect, results);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 清理
|
|
|
|
/// <summary>清空四叉树</summary>
|
|
public void Clear()
|
|
{
|
|
m_Root = null;
|
|
m_RowHeights = null;
|
|
m_ColWidths = null;
|
|
IsBuilt = false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 坐标计算
|
|
|
|
private float GetRowTopY(int row)
|
|
{
|
|
float y = 0;
|
|
for (int i = 0; i < row && i < m_RowHeights.Length; i++)
|
|
y += m_RowHeights[i];
|
|
return y;
|
|
}
|
|
|
|
private float GetRowBottomY(int row)
|
|
{
|
|
float y = 0;
|
|
for (int i = 0; i <= row && i < m_RowHeights.Length; i++)
|
|
y += m_RowHeights[i];
|
|
return y;
|
|
}
|
|
|
|
private float GetColLeftX(int col)
|
|
{
|
|
float x = 0;
|
|
for (int i = 0; i < col && i < m_ColWidths.Length; i++)
|
|
x += m_ColWidths[i];
|
|
return x;
|
|
}
|
|
|
|
private float GetColRightX(int col)
|
|
{
|
|
float x = 0;
|
|
for (int i = 0; i <= col && i < m_ColWidths.Length; i++)
|
|
x += m_ColWidths[i];
|
|
return x;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 工具
|
|
|
|
/// <summary>命名空间规范化:null、空串、默认命名空间统一为 "xeric_table_default"</summary>
|
|
private static string NormalizeNs(string ns)
|
|
{
|
|
if (string.IsNullOrEmpty(ns) || ns == "xeric_table_default")
|
|
return "xeric_table_default";
|
|
return ns;
|
|
}
|
|
|
|
private static bool RectIntersects(Rect a, Rect b)
|
|
{
|
|
return a.x < b.xMax && a.xMax > b.x &&
|
|
a.y < b.yMax && a.yMax > b.y;
|
|
}
|
|
|
|
private static Rect RectIntersection(Rect a, Rect b)
|
|
{
|
|
float x = Mathf.Max(a.x, b.x);
|
|
float y = Mathf.Max(a.y, b.y);
|
|
float xMax = Mathf.Min(a.xMax, b.xMax);
|
|
float yMax = Mathf.Min(a.yMax, b.yMax);
|
|
return new Rect(x, y, Mathf.Max(0, xMax - x), Mathf.Max(0, yMax - y));
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|