Unity/C# - Grids - Simple Grid Segment struct
This is a simple grid struct using Unity.Mathematics. It uses most of the concepts I presented before, but this time each instance can have an offset to the world. This allows different grids to have different offsets as half-planes.
using UnityEngine; using Unity.Mathematics; using System; [System.Serializable] public struct GridSeg { /// <summary> /// Grid right-vector in world space /// </summary> [SerializeField] private float2 rightVector; /// <summary> /// Grid up-vector in world space /// </summary> [SerializeField] private float2 upVector; /// <summary> /// Amount of cells in right and up direction /// </summary> [SerializeField] private int2 size; /// <summary> /// offset in world-space from origin (half-plane) /// </summary> [SerializeField] private float2 offset; /// <summary> /// Matrix to convert from grid-space to world-space /// </summary> public float3x3 GridToWorldMatrix => new float3x3( new float3(rightVector, 0), new float3(upVector, 0), new float3(offset, 1)); public float2 Right => rightVector; public float2 Up => upVector; public float2 Left => -rightVector; public float2 Down => -upVector; public int2 Size => size; public float2 Offset => offset; /// <summary> /// Global basic directions in grid-space /// </summary> public static readonly int2 GridRight = new int2(1, 0); public static readonly int2 GridLeft = new int2(-1, 0); public static readonly int2 GridUp = new int2(0, 1); public static readonly int2 GridDown = new int2(0, -1); /// <summary> /// Global digonal directions in grid-space /// Start at top right, moving counter-clockwise /// </summary> public static readonly int2[] Diagonals = new int2[] { GridRight + GridUp, GridLeft + GridUp, GridLeft + GridDown, GridRight+ GridDown }; /// <summary> /// linearize the 2d coord to 1d. First rows, then 2nd row, etc.. each gridcoord has a unique index /// </summary> /// <param name="gridCoord">coordinate in grid-space</param> /// <returns>linearized index</returns> public int Linearize(int2 gridCoord) => Contains(gridCoord) ? gridCoord.x + gridCoord.y * size.x : -1; /// <summary> /// Total amount of cells in the grid /// </summary> public int CellCount => size.x * size.y; /// <summary> /// Corner points in grid-space (cells at the end) /// Start at top right, moving counter-clockwise /// </summary> public int2[] GridCorners => new int2[] { new int2(size.x-1, size.y-1), new int2(0, size.y-1), new int2(0,0), new int2(size.x-1,0) }; /// <summary> /// world-space grid corners (world-corners of the grid-corner cells) /// Start at top right, moving counter-clockwise /// </summary> public float2[] GridWorldCorners => new float2[] { TransformPoint(GridCorners[0]) + TransformDirection(Diagonals[0]) * 0.5f, TransformPoint(GridCorners[1]) + TransformDirection(Diagonals[1]) * 0.5f, TransformPoint(GridCorners[2]) + TransformDirection(Diagonals[2]) * 0.5f, TransformPoint(GridCorners[3]) + TransformDirection(Diagonals[3]) * 0.5f }; /// <summary> /// Iterate through the world points (cell center), right them up /// </summary> public System.Collections.Generic.IEnumerable<float2> WorldPoints { get { foreach (var gridPoint in GridPoints) yield return TransformPoint(gridPoint); } } /// <summary> /// Iterate through the grid points, right them up /// </summary> public System.Collections.Generic.IEnumerable<int2> GridPoints { get { for (int i = 0; i < size.x; i++) for (int j = 0; j < size.y; j++) yield return new int2(i, j); } } /// <summary> /// Transform a direction from grids-pace to world-space /// </summary> /// <param name="gridDirection">grid-space direction</param> /// <returns>world-space direction</returns> public float2 TransformDirection(int2 gridDirection) => math.mul(GridToWorldMatrix, new float3(gridDirection.x, gridDirection.y, 0)).xy; /// <summary> /// Transform a point from grids-pace to world-space /// </summary> /// <param name="gridPoint">grid-space point</param> /// <returns>world-space point</returns> public float2 TransformPoint(int2 gridPoint) => math.mul(GridToWorldMatrix, new float3(gridPoint.x, gridPoint.y, 1)).xy; /// <summary> /// Transform a direction from world-space to grid-space /// </summary> /// <param name="worldDirection">world-space direction</param> /// <returns>grid-space direction</returns> public int2 TransformDirection(float2 worldDirection) => new int2(math.round(math.mul(math.inverse(GridToWorldMatrix), new float3(worldDirection, 0)).xy)); /// <summary> /// Transform a point from world-pace to grids-space /// </summary> /// <param name="worldPoint">world-space point</param> /// <returns>grids-space point</returns> public int2 TransformPoint(float2 worldPoint) => new int2(math.round(math.mul(math.inverse(GridToWorldMatrix), new float3(worldPoint, 1)).xy)); /// <summary> /// Check if a world-point is inside the grid /// </summary> /// <param name="worldPoint">the point in world-space</param> /// <returns>true if the point is within grid boundaries</returns> public bool Contains(float2 worldPoint) { return Contains(TransformPoint(worldPoint)); } /// <summary> /// Check if a grid-point is inside the grid bounds /// </summary> /// <param name="gridPoint">the point in grid-space</param> /// <returns>true if the point is within grid boundaries</returns> public bool Contains(int2 gridPoint) { return gridPoint.x >= 0 && gridPoint.y >= 0 && gridPoint.x < size.x && gridPoint.y < size.y; } /// <summary> /// Return world corners for a certain grid point /// </summary> /// <param name="gridPoint">tthe grid-space point</param> /// <returns>world-space corner points</returns> public float2[] WorldCornersForGridPoint(int2 gridPoint) { var center = TransformPoint(gridPoint); return new float2[] { center + TransformDirection(Diagonals[0]) * 0.5f, center + TransformDirection(Diagonals[1]) * 0.5f, center + TransformDirection(Diagonals[2]) * 0.5f, center + TransformDirection(Diagonals[3]) * 0.5f }; } public static implicit operator RectInt(GridSeg seg) { return new RectInt(0, 0, seg.size.x, seg.size.y); } }

The picture shows in red the grid points, blue grid point corners and green grid corners. These 2 half-plane grid segments exist in the same continuous 2D space. It also provides functions to transform from world-space to grid-space and grid-space to world-space by using matrices (float2 is considered world-space and int2 grid-space).
If you want the closest grid-point to a world-point, just transform from world to grid.
Now, following the MVC pattern, let's make a View for the GridSeg Model.
using Unity.Mathematics; using UnityEngine; public class GridGizmos : MonoBehaviour { public GridSeg gridSeg; private void OnDrawGizmos() { Gizmos.color = Color.red; foreach (var graphVertex in gridSeg.WorldPoints) { Gizmos.DrawWireSphere(new Vector2(graphVertex.x, graphVertex.y), 0.1f); } Gizmos.color = Color.blue; foreach (var gridPoint in gridSeg.GridPoints) { foreach (var corner in gridSeg.WorldCornersForGridPoint(gridPoint)) Gizmos.DrawWireSphere(new Vector2(corner.x, corner.y), 0.1f); } Gizmos.color = Color.green; foreach (var worldPoint in gridSeg.GridWorldCorners) { Gizmos.DrawWireSphere(new Vector2(worldPoint.x, worldPoint.y), 0.1f); } var pos = new float2(transform.position.x, transform.position.y); if (!gridSeg.Contains(pos)) Gizmos.color = Color.white; else Gizmos.color = Color.magenta; Gizmos.DrawWireSphere(transform.position, 0.1f); var snappedToGrid = gridSeg.TransformPoint(pos); var snappedCenter = gridSeg.TransformPoint(snappedToGrid); Gizmos.color = Color.cyan; Gizmos.DrawLine(new Vector3(pos.x,pos.y), new Vector3(snappedCenter.x, snappedCenter.y)); } }

GridSeg is the model. The variable gridSeg is the data and the class GridGizmos is the view.
Why a struct? Well, 2 grid segments, with the same offset, same vectors and same size are essentially the same.
Comments
Post a Comment