Trouble creating my own building tools

I have been trying to create a building tool where I can freely move the part with my mouse from left to right, up to down, and vice versa. For example, if I use the front sphere to move my part, if I move my mouse in the opposite direction from the front sphere which represents the lookVector of the part that I’d be able to move it in the opposite direction as well. Think of it as the normal Roblox Studio default building tools. My part moves in a jittery motion and the Y axis is very buggy, I am trying to see if I can fix this. This script below is done in a local script and can be tested just by copying it and pasting it into Studio.

The function most important is the one at the bottom that activates while dragging the mouse, I have attempted to use dot products to know which direction the mouse is at relative to the sphere’s position.

local increment = 1 -- stud(s)
local newPart = Instance.new("Part")
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local tableOfControlSpheres

local function movePartInDirection(part, unitVector)
	part.Position = part.Position + (unitVector*increment)
	for i, v in pairs(tableOfControlSpheres) do
		v.Part.Position = v.Part.Position + (unitVector*increment)
	end
end

local function createControls(partForControls)
	tableOfControlSpheres = {
		["FrontSphere"] = {
			RepresentingVector = partForControls.CFrame.LookVector,
			Part = nil,
			Color = BrickColor.new("Dark blue")
		},
		["BackSphere"] = {
			RepresentingVector = -partForControls.CFrame.LookVector,
			Part = nil,
			Color = BrickColor.new("Dark blue")
		},
		["UpSphere"] = {
			RepresentingVector = partForControls.CFrame.UpVector,
			Part = nil,
			Color = BrickColor.new("Bright green")
		},
		["DownSphere"] = {
			RepresentingVector = -partForControls.CFrame.UpVector,
			Part = nil,
			Color = BrickColor.new("Bright green")
		},
		["LeftSphere"] = {
			RepresentingVector = -partForControls.CFrame.RightVector,
			Part = nil,
			Color = BrickColor.new("Really red")
		},
		["RightSphere"] = {
			RepresentingVector = partForControls.CFrame.RightVector,
			Part = nil,
			Color = BrickColor.new("Really red")
		}
	}
	for i, v in pairs(tableOfControlSpheres) do
		local sphere = Instance.new("Part")
		sphere.Shape = Enum.PartType.Ball
		sphere.Size = Vector3.new(1, 1, 1)
		sphere.Name = i
		sphere.CFrame = partForControls.CFrame + (v.RepresentingVector*2)
		sphere.Anchored = true
		sphere.CanCollide = false
		sphere.BrickColor = v.Color
		sphere.Transparency = 0.5
		sphere.Parent = partForControls
		v.Part = sphere
	end
end

local function insertPartIntoWorld(size, pos)
	newPart.Size = size
	newPart.Position = pos
	newPart.Anchored = true
	newPart.Parent = workspace
	createControls(newPart)
end

insertPartIntoWorld(Vector3.new(2, 2, 2), Vector3.new(0, 5, 0))

local dragging
local rs = game:GetService("RunService")
mouse.Button1Down:Connect(function() -- while dragging part
	local target = mouse.Target
	if not target then
		return
	end
	local targetInTable = tableOfControlSpheres[target.Name]
	if not targetInTable then
		return
	end
	dragging = true
	repeat
		rs.RenderStepped:Wait()
		local mousePos = mouse.Hit.Position
		local magnitude = (mousePos - target.Position).Magnitude
		if magnitude >= increment then
			local representingVector = targetInTable.RepresentingVector
			local dotProduct = targetInTable.RepresentingVector:Dot(mousePos - target.Position)
			if dotProduct > 0 then
				movePartInDirection(newPart, (targetInTable.RepresentingVector))
			elseif dotProduct < 0 then
				movePartInDirection(newPart, (-targetInTable.RepresentingVector))
			end
		end
	until not dragging
end)

mouse.Button1Up:Connect(function()
	dragging = false
end)

EDIT 1: Removed useless code

EDIT 2: Replaced the old problem with the new problem description. I have replaced the dot product with the new better dot product calculation.

1 Like

First off, the dot product alone does not determine the direction of the mouse. Use the following formula:

A dot B = |A| × |B| × cos(y)

where y is the angle between the two vectors. Rearrange this to find the angle:

y = acos([A dot B]/|A||B|)

The following edit to your code seems to have given the desired behaviour. I did not bother to change the variable names, but dotProduct is now an angle in radians; 1.57 radians is approximately math.pi/2, which corresponds to 90°.

local dotProduct = math.acos((mousePos - target.Position):Dot(targetInTable.RepresentingVector)/(mousePos - target.Position).magnitude/targetInTable.RepresentingVector.magnitude)
if math.abs(dotProduct) < 1.57 then
	movePartInDirection(newPart, (targetInTable.RepresentingVector))
elseif math.abs(dotProduct) > 1.57 then
	movePartInDirection(newPart, (-targetInTable.RepresentingVector))
end

There is another issue with this code, though, and that is that mouse.Hit is highly dependent on the angle at which you view the part and other parts that your mouse can hover over. This affects the vertical axis the most if you are viewing the part from a high angle onto a flat ground (e.g. an empty Baseplate). This is because any mouse.Hit on the Baseplate will appear to be lower than the part, and therefore the part will keep moving down, regardless of how it looks to you. If you drag the part vertically with your mouse hovering over a flat wall behind it, then it will act as expected.

1 Like

I believe it would be better practice to calculate the plane that the part should move along, and then calculate the mouse ray’s intersection with that plane. Camera:ScreenPointToRay and such.

1 Like

Thank you for the answer after I waited a while! I usually somehow forget the use of trigonometry in this case despite the dot product actually being related to trigonometry. I appreciate the clear explanation.

Would the Mouse.Hit issue you explained be the reason why it seems jittery? The part will sometime jiggle in a back and forth positive and negative direction that creates that jittery appearance while moving it.

So I should create a flat part that serves as a plane that the part can move along? I think Camera:ScreenPointToRay is perfect in this case.