Dragger tool made using raycasts has a few issues

for snapping to a grid you want to be rounding the position. You can do math.round(input/grid) * grid to achieve this where input is the raw position numbers and grid is your grid size. Say you want a grid size of 2 and the X component of the raycast position is 10.16. the math looks like this:

10.16/2 is 5.08
5.08 rounded is 5
5 * 2 is 10

this leaves you with a nice whole number that you can feed into the parts X position.

for keeping the part from clipping you need to account for part size. this differs depending on what side of the part is colliding with the thing you are placing it on as the part isnt a perfect cube. You need to use Normals for this. Normals are vectors pointing away from a surface, here is a visualization:


in this case the surface we are looking at the normal for is pointing perfectly upwards along the Y axis. this means the normal is (0, 1, 0) as the surface points in the positive Y direction. you can then use these normals to figure out how much part size offset and rotation to apply. The math behind this is a bit hard to explain unless you already have an understanding of how vectors work but I will give a very basic synopsis:

local position = castedray.Position

local up = castedray.Normal.Unit
local right = up:Cross(Vector3.zAxis).Unit
local back = right:Cross(up).Unit

target.CFrame = CFrame.fromMatrix(
	position,
	right,
	up,
	back
)

this is how we are rotating the part. We are saying the normal of the face we hit is “up”. we are then using our up reference to find a right and a back vector. Now we have three axes of rotation and can construct a CFrame that is rotated properly.

local cf = target.CFrame
local size = target.Size
local normal = castedray.Normal

local offset =
	math.abs(normal:Dot(cf.RightVector)) * size.X / 2 +
	math.abs(normal:Dot(cf.UpVector)) * size.Y / 2 +
	math.abs(normal:Dot(cf.LookVector)) * size.Z / 2

local position = castedray.Position + normal * offset

this is how we are offsetting the part so it doesnt clip. :Dot is basically checking how aligned two normals are, heres a better explanation of it. we then use the results from :Dot to scale our offset based on the size of the part. Then we apply the offset along the normal of the surface we are placing the part on.

The finished mouse.Move function looks like this:

mouse.Move:Connect(function()
	local params = RaycastParams.new()
	params.ExcludeInstances = {target}

	local castedray = workspace:Raycast(
		mouse.UnitRay.Origin,
		mouse.UnitRay.Direction * 1000,
		params
	)

	local normal = castedray.Normal.Unit

	local up = normal
	local right = up:Cross(Vector3.zAxis).Unit
	local back = right:Cross(up).Unit

	local rotation = CFrame.fromMatrix(
		Vector3.zero,
		right,
		up,
		back
	)

	local size = target.Size

	local offset =
		math.abs(normal:Dot(rotation.RightVector)) * size.X / 2 +
		math.abs(normal:Dot(rotation.UpVector)) * size.Y / 2 +
		math.abs(normal:Dot(rotation.LookVector)) * size.Z / 2

	local position = castedray.Position + normal * offset

	position = Vector3.new(
		math.round(position.X/gridSize) * gridSize,
		math.round(position.Y/gridSize) * gridSize,
		math.round(position.Z/gridSize) * gridSize
	)

	target.CFrame = CFrame.new(position) * rotation
end)

you also need a gridSize variable to determine how many studs you want each grid increment to be. I get this is probably really confusing so feel free to ask questions if you want to know how any of it works!

2 Likes