Placement system not snapping to relative grid correctly

I’m working on a placement system which snaps object positions relative to a part’s surface. The problem is that it only works correctly with a grid size of 1 stud on any part which has a size of at least 2 studs on all axis, but it’s not snapping properly with some differently sized parts and grid sizes. The object can go inside parts or float above parts. I’ve added some images below so you can see this behavior.

Grid size: 1 stud; Correct behavior
image
Grid size: 5 studs; Object goes inside part
image
Grid size: 5 studs; Object floats above part
image

Snap position calculation code:

function PlacementUtils.snapToGrid(position)
	-- Position is position gotten by ray at mouse location, grid size is 1 stud
	return Vector3.new(
		math.floor(position.X / PlacementUtils.gridSize + 0.5) * PlacementUtils.gridSize, 
		math.floor(position.Y / PlacementUtils.gridSize + 0.5) * PlacementUtils.gridSize, 
		math.floor(position.Z / PlacementUtils.gridSize + 0.5) * PlacementUtils.gridSize
	) 
end 

Main code:

-- Casts a ray every frame, targetPart is the part the ray hit, previewObject is the object to position
local objectSpace = Placement.targetPart.CFrame:PointToObjectSpace(raycastResult.Position)
local snapped = PlacementUtils.snapToGrid(objectSpace)
local worldSpace = Placement.targetPart.CFrame:PointToWorldSpace(snapped)
-- Makes the part face properly and rotates the part; rotation step is math.pi / 4
local lookCFrame = CFrame.lookAt(worldSpace, worldSpace + raycastResult.Normal) * CFrame.new(0, 0, -Placement.previewObject.Hitbox.Size.Z * 0.5)
local finalCFrame = lookCFrame * CFrame.Angles(0, 0, Placement.rotation)

Placement.placeCFrame = finalCFrame
Placement.previewObject:PivotTo(finalCFrame)

I read a lot of topics and resources on how to create a system like this to solve my problem but nothing has worked so far. I’m pretty sure this is some mistake I made with my calculations. Can anyone help me figure out what is causing this behavior or fix it?

2 Likes

Your problem is that you use grid for all 3 axises, while you need only 2. Which ones? If it’s sides of X, then you should snap only Y and Z, if it’s Y sides, then snap X and Z, and Z sides should snap X and Y only.
Why everything work ok for first image? because smaller grid = less bugs visible.

2 Likes

If I only include 2, one axis is left un-snapped, I want to include all 3 axis so it snaps on all sides of a part. I also want to have any grid size work. Any ideas as to how I could do this?

1 Like

You don’t really need to snap by all 3 axis, otherwise it will act like in your case, because surface you place your furniture on is 2d plane.
image

3 Likes

Oh that makes so much sense! How would I know on which axis to snap?

2 Likes
  1. Get mouse position relative to part’s CFrame
    So you will get Vector3, resembling where mouse look on part.
  2. Get most matching axis from part’s size and mouse absolute relative position * 2.
    So, if you will hover over Part with size (10, 20, 30), in most cases it will give you at least 1 axis matching, like (0.1234124, -9.13412, 15), so you can say that now mouse hovering on side related to Z-axis
    I say MOST cases, because floating point. It won’t be exactly 15 like in all cases, but 14.999999999999992175239 usually.
3 Likes

I see, so for the second step I’ll need to check X, Y, and Z and find which is closest to the same axis on the relative mouse position * 2?

2 Likes

You have… part. again.
image
It’s size is X:10, Y:20, Z:30

You want to place smth to it, and for this you point your mouse on it:
image
Now, we should check, what side of this part is closest to mouse dot.

local Part = --That part
local Mouse = --mouse instance

--some code related to RunService or Mouse.move
local MouseRelative = (Part.CFrame:Inverse() * CFrame.new(Mouse.Hit.Position)).Position
local SideWeights = Vector3.new(math.abs(MouseRelative.X) * 2 / Part.Size.X, math.abs(MouseRelative.Y) * 2 / Part.Size.Y, math.abs(MouseRelative.Z) * 2 / Part.Size.Z)
--So we got Vector3 with values, representing how close to certain axis
--fases our mouse. If you need get certain side and not axis,
--remove math.abs()
if SideWeights.X > SideWeights.Y and SideWeights.X > SideWeights.Z then
    --Mouse pointing on X-axis side
elseif SideWeights.Y > SideWeights.X and SideWeights.Y > SideWeights.Z then
    --Mouse pointing on Y-axis side
else
    --Mouse pointing on Z-axis side
end
2 Likes

Can also just take the normal of raycast, invert it, and then transform by deltraTansformation * invertedResult.

5 Likes

My problem is fixed, I had to change up the mouse part because I use the newer API. I cannot express my gratitude to you for helping me solve this problem and explaining how to code it. Thank you.

3 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.