Grid snapping with a rotation offset

This has been something I’ve thought about for a long time but I’ve never been able to figure out a good solution for it, I’ve made countless grid snapping systems, they’re pretty easy, but taking the grid and using a CFrame as an origin/rotation is a completely different beast

A “normal” grid aka a grid aligned with roblox’s default axi is simple, and in all of these I’m just going to be worrying about 2 dimensions, X and Z

Simple grid of 4:
Vector3.new(math.floor(Position.X/4)*4,Position.Y,math.floor(Position.Z/4)*4)

But if you want this grid rotated at say 45 degrees, suddenly this approach does not work at all. It would seem to me that the next most logical approach would be using LookVector and RightVector and solving the nearest multiple from the origin point. Can anyone think of a way to do this without it being a brute force?

The only thing I can think of would be to continually increment how many times the LookVector gets multiplied until the remaining vector is a multiple of the RightVector, but this would be terribly inefficient and now that I’m thinking about it I don’t even think it would solve my issue because I still wouldn’t know where the end point would be

The line I typed above is great because I can just put in any vector and receive the nearest point out and the math doesn’t take any variable amount of time if it’s further or closer to 0. Does anyone know a way I could do this at an angle, or at least have an idea?

1 Like

Actually now I’m thinking, I could probably create a ray using the lookvector, and then use closest point on that ray to get the right angle, and then use THAT and round off the magnitudes and then use those to multiple the lookvector and rightvector which would give me the closest point on the grid? I’ll try this.

1 Like

Can’t you just take the mouse.hit.p, then put it into the object space of the origin, then find which cell of the grid it’s in?

3 Likes
Proof of Concept
local grid = {}
local gridOrigin = CFrame.new() * CFrame.fromAxisAngle(Vector3.new(0,1,0), math.pi/4)
local gridStep = 5

for x = 1,4 do
	grid[x] = {}
	for y = 1,4 do
		local objectSpaceVec = Vector3.new(x * gridStep, 1, y * gridStep)
		grid[x][y] = CFrame.new(gridOrigin:vectorToWorldSpace(objectSpaceVec)) * CFrame.fromAxisAngle(Vector3.new(0,1,0), math.pi/4)
	end
end

function round(x, decimalPlaces)
	local mult = 10^(decimalPlaces or 0)
	return math.floor(x * mult + 0.5) / mult
end

function getCell(p)
	local gridSpace = gridOrigin:vectorToObjectSpace(p)
	local x = round(gridSpace.x/gridStep)
	local y = round(gridSpace.z/gridStep)
	if grid[x] and grid[x][y] then
		return grid[x][y]
	end
end

mouse.Move:connect(function()
	local hit = mouse.hit.p
	local gridCF = getCell(hit)
	if gridCF then
		part.CFrame = gridCF
	end
end)

If you want to see it in action (or want to copy all the code): RotatedGrid.rbxl (13.2 KB)

Thanks for the distraction, now it’s past my bedtime and I need sleep. :yum:

5 Likes

:0 I have never used object space or world space before, Thanks! I gotta learn how to use these now but this is perfect

I asked the question a while back. The guy provided some nice code as an example. I ended up using something different from his but that code helped me get it working correctly.

How would you adapt this to work in 3D space for xyz coordinates instead of solely 2d xy?
(Also I don’t understand the code defining the grid origin and how I can set the grid origin anywhere in the world)

Have you tried just using object and world space conversions? You can pmuch just have a vector or a cframe and convert it into an offset from the original or you can also use it like an offset with local transforms from the original.

To create a 3d grid, just turn the 2d array into a 3d array:

for x = 1,4 do
	grid[x] = {}
	for y = 1,4 do
        grid[x][y] = {}
        for z = 1,4 do
		    local objectSpaceVec = Vector3.new(x * gridStep, y * gridStep, z * gridStep)
		    grid[x][y][z] = CFrame.new(gridOrigin:vectorToWorldSpace(objectSpaceVec)) * (gridOrigin - gridOrigin.p)
            -- creates a CFrame at the position (x,y,z) then transforms it into the object space of gridOrigin
        end
	end
end

You’ll also have to adjust the getCell function and play around with mouse movement to get the desired effect (consider a Fortnite grid vs a Minecraft grid style).

As for your other question: Imagine your grid is a piece of graph paper (2d example, same for 3d). Your grid encompasses everything on that graph paper. The bottom left corner of your graph paper is gridOrigin. If you move your graph paper to the right 5 feet, gridOrigin moves to the right 5 feet. If you rotate around the bottom left corner 45 degrees, gridOrigin rotates 45 degrees.

3 Likes

this was a useful thread for me, I was trying to do something more complicated with dot product to get the same effect, but this is much simpler. I’m going to drop my code which doesn’t depend on having a predefined grid in a table like you guys are doing so other people can use in the future.

local PlotOrigin = CFrame.new() * CFrame.fromAxisAngle(Vector3.new(0,1,0), math.rad(Plot.Orientation.Y)

local function round(x, m)
    return math.floor((x/m) + .5) * m
end


function SnapToGrid(position, increment, objectSize)
    local relativePosition = PlotOrigin:vectorToObjectSpace(position)
    local relativeSize = PlotOrigin:vectorToObjectSpace(objectSize)
	local xOffset = (relativeSize.X/2) % increment
	local zOffset = (relativeSize.Z/2) % increment

    local newRelativePosition = Vector3.new(round(relativePosition.X, increment)+xOffset,
        position.Y, round(relativePosition.Z, increment)+zOffset)
    return PlotOrigin:vectorToWorldSpace(newRelativePosition)
end

this is a slightly adjusted version to make it more generic, so it might break lol, lmk if it does and I’ll help you out

1 Like

hey im trying to do the same thing but im not sure what objectSize is here. how should this be used?

objectSize is just Part.Size

If your model is multiple parts, you should use an invisible root part that encompasses the entire model (similar to a hitbox)

oh alright, thanks for making this code public and responding all these months later!

i am having one issue though, and i cant really diagnose it because im not great at understanding vector math and cframes etc. the x value of the relative size is giving me roughly 0, which makes the object snap to a line rather than the center of a box.

edit: ive made the plot rotate slowly to see how rotation influences it and…

nvm, with a lot of tweaking i got this:

local function round(x, m)

	return (math.floor((x/m) + 1) * m) - (m / 2)
end


function SnapToGrid(position, increment, objectSize, plot)

	local PlotOrigin = plot.CFrame
	local relativePosition = PlotOrigin:PointToObjectSpace(position.Position)
	local newRelativePosition = CFrame.new(Vector3.new(
		round(relativePosition.X, increment),
		0,
		round(relativePosition.Z, increment)
		)) * CFrame.Angles(position:ToOrientation()) 
	return PlotOrigin:ToWorldSpace(newRelativePosition), Vector2.new(round(relativePosition.X, increment) / increment, round(relativePosition.Z, increment) / increment)
end

this works perfectly for me, it takes in a cframe position so that it can also make the rotation match, the grid size as increment, the size of the model, and the plot itself

1 Like