use DrawRay to calculate the row and column of a cell in a grid

Hi everyone,

I’m trying to put 1’s in my 8x8 grid (int [, ] grid) where the pentamino X has been dropped (in the grid).
The rest (empty cells) are worth 0.
I use a RayCast that starts from the center of the pentamino and goes down to touch the grid planes and find out which grid cell has been touched (from 0 to 63).
To do this, I use the following code:

    void OnMouseUp()
    {
        UpdateRowAndCol();
        UpdateGridWithPentaminoX(row, col); // Mettre à jour la grille avec les valeurs de ROW et COL
        PrintGrid();
        PrintGrid2();
        PrintGrid();
    }

    int index;
    void UpdateRowAndCol()
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit hitInfo;
        if (Physics.Raycast(ray, out hitInfo, Mathf.Infinity, layerMask))
        {
            if (hitInfo.collider.gameObject != gameObject)
            {
             
                if (int.TryParse(hitInfo.transform.gameObject.name, out index))
                {
                    row = (index - 1) / 8; // Mettre à jour row
                    col = (index - 1) % 8; // Mettre à jour col
                    if (col == 0)
                    {
                        col = 8;
                    }
                }
            }
        }
    }

    void Update()
    {
        Ray ray = new Ray(transform.position, Vector3.down);
        if (Physics.Raycast(ray, out hit))
        {
            Debug.DrawRay(cube.transform.position, Vector3.down * 20, Color.green);
            Debug.Log("HIT: Plan touché = " + hit.transform.gameObject.name + ", Row = " + row + ", Col = " + col);
        }
        else
        {
            Debug.DrawRay(cube.transform.position, Vector3.down * 20, Color.red);
            Debug.Log("Le rayon ne touche pas d'objet.");
        }
    }


    void UpdateGridWithPentaminoX(int row, int col)
    {
        for (int i = 0; i < 5; i++)
        {
            for (int j = 0; j < 5; j++)
            {
                if (pentaminoX[i, j] == 1)
                {
                    // Vérifier si les coordonnées sont à l'intérieur des limites de la grille
                    if (row + i >= 0 && row + i < 8 && col + j >= 0 && col + j < 8)
                    {
                         grid[row + i, col + j] = 1;
                    }
                }
            }
        }
    }

    void PrintGrid()
    {
        string gridStr = "Grid:\n";
        for (int i = 0; i < 8; i++)
        {
            for (int j = 0; j < 8; j++)
            {
                gridStr += grid[i, j] + " ";
            }
            gridStr += "\n";
        }
        Debug.Log(gridStr);
        Debug.Log("HIT: Plan touché = " + hit.transform.gameObject.name + ", Row = " + row + ", Col = " + col);
    }

The problem is that Debug.Log() always returns 0 in the Row and Col.
Here’s how to set up Debug.Log():
Debug.Log("Row = " + row + ", Col = " + col); (they are calculated in void UpdateRowAndCol())
But it still returns 0.
Here’s an image showing pentamino X and its raycast (the latter is red but when it encounters a grid plane it turns green (see void Update) and returns a number from 0 to 63 (the names of small planes that form the grid). The small planes that form the red grid have a size of 1.6f (like the pentamino cubes).

Thanks for your help,

A+

It may help to add Debug.Log statements in each of the “if” statements of UpdateRowAndCol to see if you are actually reaching the row and column calculation.
It would also be a good idea to print out hitInfo.transform.gameObject.name to see if you are hitting the expected object.

1 Like

Hi everyone,

I was inspired by this little program (to calculate the 0’s and 1’s in the grid):

The only problem is that it doesn’t display a 1 on all the parent cubes (it does set the parent to 0).

Here is the code, can you help me:

[ExecuteAlways]

    int z, x;

    public Transform[] Figures;

    private void OnValidate()
    {
        grid = new int[8, 10];
    }

    private void Update()
    {
        var changedFigures = Figures.Where(i => i.hasChanged);

        if (changedFigures.Count() != 0)
        {
            ResetGrid();

            foreach (var figure in changedFigures)
            {
                foreach (Transform box in figure)
                {                   
                    int x = Mathf.FloorToInt((box.position.x / 0.8f) * 0.8f);
                    int z = Mathf.FloorToInt((box.position.z / 0.8f) * 0.8f);

                    if (PositionIsValid(x, z))
                    {
                        grid[z, x] = 1;
                    }
                }
            }
        }
    }

    private void ResetGrid()
    {
        for (int z = 0; z < GridSizeZ; z++)
        {
            for (int x = 0; x < GridSizeX; x++)
            {
                grid[z, x] = 0;
            }
        }
    }

Also, I’d like it if you drop a pentamino in the top left corner, then the 0s and 1s update in the top left.

Here’s a video with more details:

Thanks for your help,

A+

Hello everyone,

Here is the problem I am having:
in the following tuto:

the program works well, but only if the pentominoes have a size of 1f, whereas I use 1.6f pentominoes and this completely changes the grid update and puts 1s everywhere.

Perhaps by changing the following code:

 int x = Mathf.FloorToInt((box.position.x / 1.6f) * 1.6f);
int z = Mathf.FloorToInt((box.position.z / 1.6f) * 1.6f);

in the following code:

    private void Update()
    {
        var changedFigures = Figures.Where(i => i.hasChanged);

        if (changedFigures.Count() != 0)
        {
            ResetGrid();

            foreach (var figure in changedFigures)
            {
                foreach (Transform box in figure)
                {
                    int x = Mathf.FloorToInt((box.position.x / 1.6f) * 1.6f);
                    int z = Mathf.FloorToInt((box.position.z / 1.6f) * 1.6f);

                    if (PositionIsValid(x, z))
                    {
                        grid[z, x] = 1;
                    }
                }
            }
        }
    }

Your help is most welcome,

A+

I’m not going to help you (with regard to your specific questions), mostly bc I don’t have time right now, but I still wanted to share some general knowledge on the subject.

First of all you want to split your logic into a) model, and b) representation. You already have a model (to an extent) by having a 0/1 representation of the grid so you know which cell is occupied and which one is vacant. All state evaluation should query the model (for performance and accuracy), while the representation (incl. UI) should strictly maintain synchronization.

In your case it makes sense to feed input from representation via ray casts, but there are many ways this can be done as well.

When it comes to grid behavior (in a continuous space), what you’re doing here is a transformation that is known as Xstep where X is a floor/ceil/round type of behavior and step denotes that the result is mathematically a step function (i.e. discontinuous). It is useful to stay on top of these things by clumping such transformations in bite size operations.

Here’s how I defined all three

/// <summary> Rounds down floating point value to the first lesser regular interval. </summary>
static public float floorStep(float n, float d, float o = 0f)
  => d == 0f? n : d * System.MathF.Floor((n - o) / d + o;

/// <summary> Rounds up floating point value to the first greater regular interval. </summary>
static public float ceilStep(float n, float d, float o = 0f)
  => d == 0f? n : d * System.MathF.Ceiling((n - o) / d) + o;

/// <summary> Rounds floating point value to the nearest regular interval. </summary>
static public float roundStep(float n, float d, float o = 0f)
  => d == 0f? n : d * System.MathF.Round((n - o) / d) + o;

where n = value, d = grid step, o = grid’s zero offset

Now when you have this you can define a compound Vector2 variant.
For this I usually make ForEach helpers (inside a static class)

static public Vector2 ForEach(this Vector2 v, Func<int, float, float> lambda)
  => new Vector2(lambda(0, v[0]), lambda(1, v[1]));

And now you can do

static public Vector2 roundStep(Vector2 v, Vector2 grid, Vector2 origin = default)
  => v.ForEach( (i,v) => roundStep(v, grid[i], origin[i]) );

Now if you need to run this on XZ, you need some more helpers

static public Vector2 v2_xz(Vector3 v) => new Vector2(v.x, v.z);
static public Vector3 v3_xz(Vector2 v, float y = 0f) => new Vector3(v.x, y, v.y);

Finally

var grid = new Vector2(1.6f, 1.6f);
box.transform.localPosition = v3_xz(roundStep(v2_xz(box.transform.localPosition), grid));

Is one way of sorting this out. You can do a similar thing for ints if you use Vector2Int. For example

var grid = new Vector2(1.6f, 1.6f);
var (x, z) = v2int(roundStep(v2_xz(box.transform.localPosition), grid)).AsTuple();

where

static public Vector2Int v2int(Vector2 v) => new Vector2Int((int)v.x, (int)v.y);
static public (int x, int y) AsTuple(this Vector2Int v) => (v.x, v.y);

Maybe this is not your final production code (it’s sometimes advantageous to lower it for better speed and compiler optimization), but it’s immensely good for prototyping, nearly all equations are single-line, can be glanced over, and you are flexible and unlikely to produce errors by omission or needless repetition. Also vectors are packed as single entities (which they logically are), so you don’t need to constantly pack and unpack their constituents and repeat the same formulas over and over, which is especially important when working in 3D.

Additionally, when you adapt your coding style to micro-libraries and helpers that suit you, you can very easily migrate existing computation to something like Unity Mathematics or shader code.

I apologize in advance if you can’t do anything with this information for your particular problem, I just thought to share a different approach to your text book example.

Alternatively, you can bake all of this into a dedicated function that does it all

var grid = new Vector3(1.6f, 0f, 1.6f);
var (x, _, z) = getIndiceFromPosition(box.transform.localPosition, grid);
// here you can use x and z like normal variables


(int x, int y, int z) getIndiceFromPosition(Vector3 p, Vector3 grid, Vector3 origin = default) {
  return ( roundStepInt(0), roundStepInt(1), roundStepInt(2) );

  int roundStepInt(int i) // this local function makes use of by ref capturing
    => (int)(grid[i] == 0f? p[i]
          : grid[i] * System.MathF.Round((p[i] - origin[i]) / grid[i]) + origin[i]);
}

I guess my point is that the more you encapsulate complex operations the more you can build on top of that with less errors. Makes you more agile and you can solve your issues faster, even if you’re lazy.

Edit:
Btw conversion to ints doesn’t work properly if your grid is set to decimal values. Grid values must consist of natural numbers (read: positive integers) exclusively for that to work. This is all just a thought experiment.