using System; using System.Collections.Generic; using UnityEngine; namespace Flexalon { /// /// Use a grid layout to position children at fixed intervals. /// Objects are placed in cells in column-row-layer order. /// [AddComponentMenu("Flexalon/Flexalon Grid Layout"), HelpURL("https://www.flexalon.com/docs/gridLayout")] public class FlexalonGridLayout : LayoutBase { /// The type of cell to use on the column-row axes. public enum CellTypes { /// A rectangular cell. Rectangle, /// A hexagonal cell. Hexagonal } [SerializeField] private CellTypes _cellType = CellTypes.Rectangle; /// The type of cell to use on the column-row axes. public CellTypes CellType { get { return _cellType; } set { _cellType = value; _node.MarkDirty(); } } [SerializeField, Min(1)] private uint _columns = 3; /// The number of columns in the grid. public uint Columns { get { return _columns; } set { _columns = System.Math.Max(value, 1); _node.MarkDirty(); } } [SerializeField, Min(1)] private uint _rows = 3; /// The number of rows in the grid. public uint Rows { get { return _rows; } set { _rows = System.Math.Max(value, 1); _node.MarkDirty(); } } [SerializeField, Min(1)] private uint _layers = 1; /// The number of layers in the grid. public uint Layers { get { return _layers; } set { _layers = System.Math.Max(value, 1); _node.MarkDirty(); } } [SerializeField] private Direction _columnDirection = Direction.PositiveX; /// The direction of the column axis. public Direction ColumnDirection { get { return _columnDirection; } set { _columnDirection = value; _node.MarkDirty(); } } [SerializeField] private Direction _rowDirection = Direction.NegativeY; /// The direction of the row axis. public Direction RowDirection { get { return _rowDirection; } set { _rowDirection = value; _node.MarkDirty(); } } [SerializeField] private Direction _layerDirection = Direction.PositiveZ; /// The direction of the layer axis. public Direction LayerDirection { get { return _layerDirection; } set { _layerDirection = value; _node.MarkDirty(); } } /// How to determine the size of the cell. public enum CellSizeTypes { /// The object size is divided by the number of columns. Fill, /// The cell size is fixed. Fixed, } [SerializeField] private CellSizeTypes _columnSizeType = CellSizeTypes.Fill; /// How to determine the size of the columns, public CellSizeTypes ColumnSizeType { get { return _columnSizeType; } set { _columnSizeType = value; _node.MarkDirty(); } } [SerializeField, Min(0)] private float _columnSize = 1.0f; /// The fixed size of the columns. public float ColumnSize { get { return _columnSize; } set { _columnSize = Mathf.Max(0, value); _columnSizeType = CellSizeTypes.Fixed; _node.MarkDirty(); } } [SerializeField] private CellSizeTypes _rowSizeType = CellSizeTypes.Fill; /// How to determine the size of the rows. public CellSizeTypes RowSizeType { get { return _rowSizeType; } set { _rowSizeType = value; _node.MarkDirty(); } } [SerializeField, Min(0)] private float _rowSize = 1.0f; /// The fixed size of the rows. public float RowSize { get { return _rowSize; } set { _rowSize = Mathf.Max(0, value); _rowSizeType = CellSizeTypes.Fixed; _node.MarkDirty(); } } [SerializeField] private CellSizeTypes _layerSizeType = CellSizeTypes.Fill; /// How to determine the size of the layers. public CellSizeTypes LayerSizeType { get { return _layerSizeType; } set { _layerSizeType = value; _node.MarkDirty(); } } [SerializeField, Min(0)] private float _layerSize = 1.0f; /// The fixed size of the layers. public float LayerSizeSize { get { return _layerSize; } set { _layerSize = Mathf.Max(0, value); _layerSizeType = CellSizeTypes.Fixed; _node.MarkDirty(); } } [SerializeField] private float _columnSpacing = 0; /// The spacing between columns. public float ColumnSpacing { get { return _columnSpacing; } set { _columnSpacing = value; _node.MarkDirty(); } } [SerializeField] private float _rowSpacing = 0; /// The spacing between rows. public float RowSpacing { get { return _rowSpacing; } set { _rowSpacing = value; _node.MarkDirty(); } } [SerializeField] private float _layerSpacing = 0; /// The spacing between layers. public float LayerSpacing { get { return _layerSpacing; } set { _layerSpacing = value; _node.MarkDirty(); } } [SerializeField] private Align _horizontalAlign = Align.Center; /// How to align each child in its cell horizontally. public Align HorizontalAlign { get { return _horizontalAlign; } set { _horizontalAlign = value; _node.MarkDirty(); } } [SerializeField] private Align _verticalAlign = Align.Center; /// How to align each child in its cell vertically. public Align VerticalAlign { get { return _verticalAlign; } set { _verticalAlign = value; _node.MarkDirty(); } } [SerializeField] private Align _depthAlign = Align.Center; /// How to align each child in its cell in depth. public Align DepthAlign { get { return _depthAlign; } set { _depthAlign = value; _node.MarkDirty(); } } [Serializable] private class TransformList { public List Items; } [Serializable] private class CellDict : FlexalonDict {} [SerializeField, HideInInspector] private CellDict _cellToChildren; private Dictionary _childToCell; /// Returns the first child in the cell. /// The column of the cell. /// The row of the cell. /// The layer of the cell. /// The first child in the cell. public Transform GetChildAt(int column, int row, int layer = 0) { if (_cellToChildren != null) { if (_cellToChildren.TryGetValue(new Vector3Int(column, row, layer), out var children)) { return children.Items[0]; } } return null; } /// Returns all children in the cell. /// The column of the cell. /// The row of the cell. /// The layer of the cell. /// A list of children in the cell. public Transform[] GetChildrenAt(int column, int row, int layer = 0) { if (_cellToChildren != null) { if (_cellToChildren.TryGetValue(new Vector3Int(column, row, layer), out var children)) { return children.Items.ToArray(); } } return new Transform[0]; } private void SetCell(Vector3Int cell, Transform child) { if (!_cellToChildren.TryGetValue(cell, out var list)) { list = new TransformList { Items = new List() }; _cellToChildren.Add(cell, list); } list.Items.Add(child); _childToCell.Add(child, cell); } private void UpdateCells() { if (_cellToChildren == null) { _cellToChildren = new CellDict(); } if (_childToCell == null) { _childToCell = new Dictionary(); } _cellToChildren.Clear(); _childToCell.Clear(); Vector3Int nextCell = Vector3Int.zero; foreach (var child in Node.Children) { var childTransform = child.GameObject.transform; if (child.GameObject.TryGetComponent(out var cellComponent)) { SetCell(cellComponent.Cell, childTransform); } else { SetCell(nextCell, childTransform); nextCell[0]++; if (nextCell[0] >= _columns) { nextCell[0] = 0; nextCell[1]++; if (nextCell[1] >= _rows) { nextCell[1] = 0; nextCell[2]++; } } } } } private Vector3 GetCellSize(Vector3Int axes, Vector3 layoutSize) { var cellSize = layoutSize; cellSize[axes[0]] = GetColumnSize(layoutSize[axes[0]]); cellSize[axes[1]] = GetRowSize(layoutSize[axes[1]]); cellSize[axes[2]] = GetLayerSize(layoutSize[axes[2]]); return cellSize; } private Vector3 GetGridSize(Vector3Int axes, Vector3 cellSize) { int columnAxis = axes[0]; int rowAxis = axes[1]; int layerAxis = axes[2]; Vector3 gridSize = Vector3.zero; if (_cellType == CellTypes.Hexagonal && _rows > 1) { gridSize[rowAxis] = cellSize[rowAxis] + (0.75f * cellSize[rowAxis] + _rowSpacing) * (_rows - 1); } else { gridSize[rowAxis] = cellSize[rowAxis] * _rows + (_rowSpacing * (_rows - 1)); } gridSize[columnAxis] = cellSize[columnAxis] * _columns + (_columnSpacing * (_columns - 1)); if (_cellType == CellTypes.Hexagonal && _rows > 1) { gridSize[columnAxis] += cellSize[columnAxis] * 0.5f; } gridSize[layerAxis] = cellSize[layerAxis] * _layers + (_layerSpacing * (_layers - 1)); return gridSize; } private Vector3Int GetAxes() { var columnAxis = (int) Math.GetAxisFromDirection(_columnDirection); var rowAxis = (int) Math.GetAxisFromDirection(_rowDirection); var layerAxis = (int) Math.GetAxisFromDirection(_layerDirection); var otherAxes = Math.GetOtherAxes(columnAxis); if (columnAxis == rowAxis) { rowAxis = (layerAxis == otherAxes.Item1) ? otherAxes.Item2 : otherAxes.Item1; } if (columnAxis == layerAxis) { layerAxis = (rowAxis == otherAxes.Item1) ? otherAxes.Item2 : otherAxes.Item1; } if (rowAxis == layerAxis) { layerAxis = Math.GetThirdAxis(columnAxis, rowAxis); } return new Vector3Int(columnAxis, rowAxis, layerAxis); } private CellSizeTypes[] GetCellSizeTypes() { return new CellSizeTypes[3] { _columnSizeType, _rowSizeType, _layerSizeType }; } /// public override Bounds Measure(FlexalonNode node, Vector3 size, Vector3 min, Vector3 max) { FlexalonLog.Log("GridMeasure | Size", node, size, min, max); var axes = GetAxes(); var cellSize = GetCellSize(axes, size); var sizeTypes = GetCellSizeTypes(); foreach (var child in node.Children) { var childSize = child.GetMeasureSize(size); for (int i = 0; i < 3; i++) { if (node.GetSizeType(i) == SizeType.Layout && sizeTypes[i] == CellSizeTypes.Fill) { cellSize[i] = Mathf.Max(childSize[i], cellSize[i]); } } } var minCellSize = GetCellSize(axes, min); var maxCellSize = GetCellSize(axes, max); cellSize = Math.Clamp(cellSize, minCellSize, maxCellSize); var gridSize = GetGridSize(axes, cellSize); for (int i = 0; i < 3; i++) { if (node.GetSizeType(i) == SizeType.Layout) { size[i] = gridSize[i]; } } SetChildrenFillShrinkSize(node, cellSize, size); return new Bounds(Vector3.zero, size); } private float GetRowSize(float availableRowSize) { if (_rowSizeType == CellSizeTypes.Fixed) { return _rowSize; } if (_cellType == CellTypes.Rectangle) { return (availableRowSize - _rowSpacing * (_rows - 1)) / _rows; } else { return (availableRowSize - _rowSpacing * (_rows - 1)) / (1 + (_rows - 1) * 0.75f); } } private float GetColumnSize(float availableColumnSize) { if (_columnSizeType == CellSizeTypes.Fixed) { return _columnSize; } if (_cellType == CellTypes.Rectangle) { return (availableColumnSize - _columnSpacing * (_columns - 1)) / _columns; } else { var sz = (availableColumnSize - _columnSpacing * (_columns - 1)) / _columns; if (_rows > 1) { sz *= _columns / (_columns + 0.5f); } return sz; } } private float GetLayerSize(float availableColumnSize) { if (_layerSizeType == CellSizeTypes.Fixed) { return _layerSize; } return (availableColumnSize - _layerSpacing * (_layers - 1)) / _layers; } private Vector3 GetPosition(Vector3Int cell, Vector3Int axes, Vector3 cellSize, Vector3 gridSize) { var columnAxis = axes[0]; var rowAxis = axes[1]; var layerAxis = axes[2]; var columnSize = cellSize[axes[0]]; var rowSize = cellSize[axes[1]]; var layerSize = cellSize[axes[2]]; var position = -gridSize / 2; if (_cellType == CellTypes.Rectangle) { position[rowAxis] += rowSize * cell[1] + _rowSpacing * cell[1] + rowSize / 2; position[columnAxis] += columnSize * cell[0] + _columnSpacing * cell[0] + columnSize / 2; } else { bool rowEven = (cell[1] % 2) == 0; position[axes[1]] += rowSize * 0.75f * cell[1] + _rowSpacing * cell[1] + rowSize / 2; position[columnAxis] += columnSize * cell[0] + columnSize / 2 + _columnSpacing * cell[0] + (rowEven ? 0 : columnSize / 2); } position[layerAxis] += layerSize * cell[2] + _layerSpacing * cell[2] + layerSize / 2; position[rowAxis] *= Math.GetPositiveFromDirection(_rowDirection); position[columnAxis] *= Math.GetPositiveFromDirection(_columnDirection); position[layerAxis] *= Math.GetPositiveFromDirection(_layerDirection); return position; } private void PositionChild(FlexalonNode child, Vector3Int cell, Vector3Int axes, Vector3 cellSize, Vector3 gridSize) { Vector3 position; position = GetPosition(cell, axes, cellSize, gridSize); var aligned = Math.Align(child.GetArrangeSize(), cellSize, _horizontalAlign, _verticalAlign, _depthAlign); child.SetPositionResult(position + aligned); } /// public override void Arrange(FlexalonNode node, Vector3 layoutSize) { FlexalonLog.Log("GridArrange | LayoutSize", node, layoutSize); var axes = GetAxes(); var cellSize = GetCellSize(axes, layoutSize); var gridSize = GetGridSize(axes, cellSize); UpdateCells(); foreach (var child in node.Children) { if (_childToCell.TryGetValue(child.GameObject.transform, out var cell)) { PositionChild(child, cell, axes, cellSize, gridSize); } } } void OnDrawGizmosSelected() { if (_node != null) { var axes = GetAxes(); var sz = _node.Result.AdapterBounds.size - _node.Padding.Size; var cellSize = GetCellSize(axes, sz); var gridSize = GetGridSize(axes, cellSize); Gizmos.color = new Color(1, 1, 0, 0.5f); var scale = _node.GetWorldBoxScale(true); Gizmos.matrix = Matrix4x4.TRS(_node.GetWorldBoxPosition(scale, true), transform.rotation, scale); for (int r = 0; r < _rows; r++) { for (int c = 0; c < _columns; c++) { for (int l = 0; l < _layers; l++) { var position = GetPosition(new Vector3Int(c, r, l), axes, cellSize, gridSize); if (_cellType == CellTypes.Rectangle) { DrawRectangle(position, axes, cellSize); } else { DrawHexagon(position, axes, cellSize); } } } } } } void DrawRectangle(Vector3 position, Vector3Int axes, Vector3 cellSize) { var columnAxis = axes[0]; var rowAxis = axes[1]; var columnSize = cellSize[axes[0]]; var rowSize = cellSize[axes[1]]; var p1 = new Vector3(); // top right p1[rowAxis] = rowSize / 2; p1[columnAxis] = columnSize / 2; var p2 = new Vector3(); // bottom right p2[rowAxis] = -rowSize / 2; p2[columnAxis] = columnSize / 2; var p3 = new Vector3(); // bottom left p3[rowAxis] = -rowSize / 2; p3[columnAxis] = -columnSize / 2; var p4 = new Vector3(); // top left p4[rowAxis] = rowSize / 2; p4[columnAxis] = -columnSize / 2; Gizmos.DrawLine(position + p1, position + p2); Gizmos.DrawLine(position + p2, position + p3); Gizmos.DrawLine(position + p3, position + p4); Gizmos.DrawLine(position + p4, position + p1); } void DrawHexagon(Vector3 position, Vector3Int axes, Vector3 cellSize) { var columnAxis = axes[0]; var rowAxis = axes[1]; var columnSize = cellSize[axes[0]]; var rowSize = cellSize[axes[1]]; var p1 = new Vector3(); // top p1[rowAxis] = rowSize / 2; var p2 = new Vector3(); // top right p2[rowAxis] = rowSize / 4; p2[columnAxis] = columnSize / 2; var p3 = new Vector3(); // bottom right p3[rowAxis] = -rowSize / 4; p3[columnAxis] = columnSize / 2; var p4 = new Vector3(); // bottom p4[rowAxis] = -rowSize / 2; var p5 = new Vector3(); // bottom left p5[rowAxis] = -rowSize / 4; p5[columnAxis] = -columnSize / 2; var p6 = new Vector3(); // top left p6[rowAxis] = rowSize / 4; p6[columnAxis] = -columnSize / 2; Gizmos.DrawLine(position + p1, position + p2); Gizmos.DrawLine(position + p2, position + p3); Gizmos.DrawLine(position + p3, position + p4); Gizmos.DrawLine(position + p4, position + p5); Gizmos.DrawLine(position + p5, position + p6); Gizmos.DrawLine(position + p6, position + p1); } } }