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);
    }
}


2 instances of grid segments

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));
    }
}


Simple grid gizmos draw

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

Popular posts from this blog

Unity3D/C# - Asynchronous Programming - Coroutines vs await/async

C# - Simple Graph Interfaces and a MeshGraph