Snapping to nearest grid point?

I have some objects I’m spawning, but they need to spawn locked to a grid.
This might not be a significant problem, as I can use mathf.Round to round an attempted location to its nearest whole number. However, the grid I need to snap to may be offset. Usually it will be offset by exactly one half, eg, I need to snap to 0.5, 1.5, and 2.5.
(I doubt I will need to snap to anything other than a half-unit offset, but by the time I can get this resolved I’ll likely have a system that can be easily adapted to any kind of offset.)

I really don’t know how to do this, at least, not without making a complex series of if/then statements. Does anyone have any better ideas?

You round the object coordinate then add the offset.

I’ve done snapping by just using modulo operator (%) to get the remainder of a division of the snap interval, and then either subtracting the difference from its position, or adding the difference if it’s more than halfway.

Don’t have the code available to me right now, but it was probably something along these lines:

public static float SnapFloat(float value, float interval)
{
    float rem = value % interval;
  
    float halfInterval = interval / 2f;
  
    if (rem > halfInterval)
    {
        float remDif = interval - rem;
        value += remDif;
    }
    else
    {
        value -= rem;
    }
  
    return value;
}

Scenario: Object is placed at 1.652.
Nearest grid coordinate: 1.5
Rounding changes coordinate to 2.0, adding offset changes coordinate to 2.5
End result: placed at 2.5

That solution doesn’t work.

This wouldn’t quite work either. I think it woks for making snapping points that are fractions of a whole number, but I still snap to a whole number, just offset by a given value. Since this code evaluates against half the interval size, it doesn’t properly check larger values.

Well, no, it does work if you do the math.

Say you have an x position of 3.72 and you want to snap it to 0.25 intervals.

3.72 % 0.25 = 0.22

0.25 / 2 = 0.125

0.22 > 0.125 = true

because we’re overall half the interval, we then go:
0.25 - 0.22 = 0.03
Then add that to 3.72 to give us a nicely snapped 3.75.

Try out some other numbers, but it does work.

Your code is for INTERVALS, I’m looking for an OFFSET. My interval is still 1.0, but to an offset value.
If my offset were 0.25, then an x position of 3.72 would need to snap to 3.25, and 3.78 would need to snap to 4.25.

Then just… snap the value then add the offset. It’s not hard.

My formula works for intervals of any value. So you can snap to whole 1.0 intervals then just add any offset after.

It doesn’t work like that. That literally would create the same problem with what zulo3d suggested above.

This is what I’ve worked out so far, but this version only works with the offset being either zero or 0.5.
It starts getting complicated to use this method with an offset that puts the “middle range” past 1.0 or below 0.
Maybe I’ll just go with this and force my game to never need an offset other than 0.5. Apologies to anyone searching the archives for the solution to this problem.

        float PlantLocX = transform.position.x;
        float PlantLocY = transform.position.y;

        if (GridOffset.x == 0.5)
        {
            if (PlantLocX % 1 > 0.25 && PlantLocX % 1 < 0.75)
            {
                PlantLocX = Mathf.Floor(PlantLocX) + 0.5f;
            }
            else if (PlantLocX % 1 > 0.5)
            {
                PlantLocX = Mathf.Floor(PlantLocX) + 1.5f;
            }
            else
            {
                PlantLocX = Mathf.Floor(PlantLocX) - 0.5f;
            }
        }
        else
        {
            PlantLocX = Mathf.Round(PlantLocX);
        }

Then just always snap/round downwards, rather than my snapping formula that snaps to the closest interval.

Snap 1.62 down to 1, add your offset. Easy. So in a way, Zulo was correct to begin with.

I did my rounding wrong; dang it. I checked for a quarter on either side; I thought that was what I wanted since it equaled one half, but that’s not right.

This works for snapping to an offset of 0.5, but not to any arbitrary offset.
If the offset is 0.2, then a value of x.8 or higher needs to be have offset-plus-one added to it.
3.82 would snap to 4.2, not 3.2

Right, I see what you mean.

Then it’s a two stage approach. First you calculate the snapped offset position based on the objects current position (so round down + offset), then you work out the difference to this position and work out where to snap from there (whether it be down or up).

If you want to snap to a grid with an interval of “i” and an offset of “o”, the general approach is:
vSnap = round((v-i)/o)*o+i

public float Snap(float aVal, float aInterval, float aOffset)
{
    return Mathf.Round((aVal - aOffset)/aInterval) * aInterval + aOffset;
}

It’s just linear algebra. Your interval and offset essentially define a non-rotated coordinate space. So you first bring your coordinate / value into local space, round it and transform it back.

So for example Snap(someVal, 1.6f,0.2f) would produce values like
-1.4, 0.2, 1.8, 3.4, 5, …

Since in “local space” the actual grid coordinates are whole numbers (-1, 0,1,2,3, …) when rounding you would always round correctly in between those grid points.