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

  1. Caesars Palace - DrmCD
    Caesars Palace. Location. Location. Find the map. 김포 출장안마 Get to. 강릉 출장마사지 Directions. 세종특별자치 출장샵 Caesars 경주 출장안마 Palace ·. 590 Grand Ave. Las Vegas, 서귀포 출장샵 NV 89103.

    ReplyDelete

Post a Comment

Popular posts from this blog

Tools and Property Drawers

PCG and Data Analysis - Two sides of the same coin? Also, GANs?