0
\$\begingroup\$

I want to be able to move objects on a grid always by what the grid cell size is, and objects bigger than the grid size should not overlap.

The cube in this GIF below is snapping how I want (1 x 1).

enter image description here

If I then change the size of the object, it doesn't snap to the grid quite how I would like. It snaps, but overlaps. I don't want an object to overlap the grid cell.

enter image description here

The snapping code is pretty straight forward and has been posted quite a lot and seems to work fine.

pos.x = Mathf.Round(hit.point.x + (hitNormal.x * objScale.x)) * gridSize;
pos.y = Mathf.Round(hit.point.y + (hitNormal.y * objScale.y)) * gridSize;
pos.z = Mathf.Round(hit.point.z + (hitNormal.z * objScale.z)) * gridSize;

I was thinking that I need to somehow set a pivot / anchor point, but I wouldn't want that to be one corner of the object, I was thinking somehow I could find the center of the object and work from there so it can snap correctly. Not sure if this is correct, or how to work that out.

Edit: More code added to show how I am moving objects around currently.

Objects are placed in the world by ray casting. I don't use any grid array to know the location of objects on the grid.

For placement testing (i.e object inside another) this is handled already but removed that part of the code. For that I am doing a simple box overlap test which works nicely.

float gridSize = 1f;
Vector3 hitNormal = hit.normal.normalized;
Vector3 localObjScale = transform.localScale;

// - "Determine the minimum grid space this shape will use up"
// Objects will always be whole numbers.  i.e 2w x 1h x 1l (like second GIF)
int cellWidth = Mathf.RoundToInt(localObjScale.x);
int cellsHeight = Mathf.RoundToInt(localObjScale.y);
int cellsLength = Mathf.RoundToInt(localObjScale.z);

Vector3 objScale = localObjScale / 2f; // This sets the pivot to the center
Vector3 pos = Vector3.zero;

// Snap
pos.x = Mathf.Round(hit.point.x + (hitNormal.x * objScale.x)) * gridSize;
pos.y = Mathf.Round(hit.point.y + (hitNormal.y * objScale.y)) * gridSize;
pos.z = Mathf.Round(hit.point.z + (hitNormal.z * objScale.z)) * gridSize;

transform.position = pos;
\$\endgroup\$
1
\$\begingroup\$

Although the existing answer looks pretty good. Considering your existing code, and the fact that you mentioned that you already handled placement testing. I think there is another answer that is much closer to your existing code and requires less changes.

The idea is here is that the 2x1 cube has its pivot on [1, 0.5, 0.5] while the 1x1 cube has its pivot at [0.5, 0.5, 0.5].

You could fix your problem by altering the pivot so that 2x1 has a pivot of either [0.5, 0.5, 0.5] or [1.5, 0.5, 0.5]. This is basically @Chillersanim's answer. It is however still difficult to get the object to snap correctly as you want the center (1, 0.5, 0.5) to be as close to your cursor as possible, not [0.5, 0.5, 0.5] nor [1.5, 0.5, 0.5].

Another way around the problem is to substract the pivot before snapping and then add it back on after snapping. As an example:

This example is only about the x axis for simplicity.

  • If the cursor position on a grid cell is 1.3.
  • A pivot of 0.5 for a 1x1 cube would snap like this: round(1.3 - 0.5) + 0.5 = 1.5 (So cube boundaries are from 1 to 2)
  • a pivot of 1 for a 2x1 cube would snap like this: round(1.3 - 1) + 1 = 1 (so cube boundaries are from 0 to 2)

Which is exactly the result we want to get both cubes aligned on the same grid and still center correctly on the cursor position, to convert that to the code you are using, would end up something like this:

    Vector3 hitNormal = hit.normal.normalized;
    Vector3 localObjScale = transform.localScale;

    Vector3 pivot = localObjScale / 2f;

    // The offset that we need to add to place the object in front of the object we are hovering instead of inside the object we are hovering
    Vector3 normalOffset = Vector3.Scale(hitNormal, pivot);

    // Snap to grid
    Vector3Int hoveredCell = Vector3Int.RoundToInt(hit.point - pivot + normalOffset);

    // Add the pivot again after snapping.
    Vector3 pos = hoveredCell + pivot;

    transform.position = pos;

Note that this code might shift your entire grid by +0.5 on all axes, but I don't think that should be to big of a problem to fix.


Edit, rotation

As you noticed, the above code doesn't work when you rotate the object. This is because when we calculated our pivot, we ignore the rotation. So we could apply the rotation to the pivot to fix that.

In the below example I do that. There might be a single line builtin method to do this, not sure. If there is I haven't found it yet...

Vector3 localObjScale = transform.localRotation * transform.localScale;
localObjScale = new Vector3 // Make sure scale is positive after rotation :)
{
    x = Mathf.Abs(localObjScale.x),
    y = Mathf.Abs(localObjScale.y),
    z = Mathf.Abs(localObjScale.z)
};

To use this just replace the original line where you declare localObjScale with this snippet.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the help. I adapted your code and it works. However, am struggling to handle objects that can be rotated, they overlap like in my question. When I spawn in the 2x1 cube, it sets the pivot correctly, but if I rotate it around the Y by 90, it then fails to snap as I would like. Not sure how to fix this. \$\endgroup\$ – JacketPotatoeFan Aug 28 at 10:46
  • 1
    \$\begingroup\$ Edited my answer to include a section about rotation. \$\endgroup\$ – troien Aug 28 at 13:09
3
\$\begingroup\$

The problem is, that you seem to take the center of the shape as pivot.
What you want is to select a pivot, that places the shape inside the grid, so that it overlaps as few cells as possible.

How to obtain such a pivot:

  • Place shape in grid by minimum fit: Place it so, that as few cells are used as possible.
  • Calculate how many cells are affected.
  • Select a cell in the grid space that will be your pivot cell
  • Use the center of the pivot cell as placement pivot for your shape.

Diagram for visual explanation.
Calculate pivot point

When you use this pivot to place your shapes, they will not overlap to much anymore.
At the same time, you can use the calculated grid space for the shape, to determine whether a cell is being occupied or not by a shape.

Keep in mind, the visualized grid is only as a to help understanding the concept.
In generall, you just need the width and length of your shape and the size of each cell.
Then you can calculate the amount of affected cells by dividing as shown in the image.
Also if you want to always use the top left cell as pivot cell, you can skip that step as well.

\$\endgroup\$
  • \$\begingroup\$ Hi. I tried for a few hours trying to implement your solution, but am struggling big time (sorry and still beginner at this stuff) and made one big mess of it. I've added additional info and code to my question. Removed unneeded stuff to keep it very basic. Appreciate the help on this as I've been tackling this for a while now. \$\endgroup\$ – JacketPotatoeFan Aug 14 at 9:30
  • \$\begingroup\$ For the used up cell space, use Mathf.CeilToInt() instead of Mathf.RoundToInt() as otherwise you may get to little cells. Also don't use the object scale as pivot point. That wont work when you have any other shape than a uniform cube. Instead get the world bounds of your shape (transform your mesh bounds), and then use it to calculate a pivot point. That is much cleaner and less error prone. \$\endgroup\$ – Chillersanim Aug 14 at 11:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.