PointSnapped=Signal.new()function StartSnapping()if SnappingStage or SnapTracking.Enabled then return end SnappingStage="\083\116\097\114\116\105\110\103"SnapTracking.TrackEdgeMidpoints=false SnapTracking.TrackFaceCentroids=false SnapTracking.TargetFilter=function(A)return Selection.PartIndex[A]end SnapTracking.StartTracking(function(A)if A and A.p~=SnappedPoint then SnappedPoint=A.p PointSnapped:Fire(A.p)end end)Connections.SnapDragStart=Support.AddUserInputListener("\066\101\103\097\110","\077\111\117\115\101\066\117\116\116\111\110\049",false,function(A)SnappingStage="\068\105\114\101\099\116\105\111\110"SnappingStartAim=Vector2.new(A.Position.X,A.Position.Y)SnappingStartPoint=SnappedPoint SnappingStartTarget=SnapTracking.Target SnappingStartDirections=GetFaceOffsetsFromCorner(SnappingStartTarget,SnappingStartPoint)SnappingStartSelectionState=PreparePartsForResizing()AreaPermissions=Security.GetPermissions(Security.GetSelectionAreas(Selection.Parts),Core.Player)SnapTracking.StopTracking()DirectionLine=Core.Tool.Interfaces.SnapLine:Clone()DirectionLine.Parent=Core.UI DirectionLine.Visible=false TrackChange()Connections.SnapDrag=Support.AddUserInputListener("\067\104\097\110\103\101\100","\077\111\117\115\101\077\111\118\101\109\101\110\116",true,function(A)SnappingEndAim=Vector2.new(A.Position.X,A.Position.Y)ScreenSnappedPoint=Workspace.CurrentCamera:WorldToScreenPoint(SnappingStartPoint)ScreenSnappedPoint=Vector2.new(ScreenSnappedPoint.X,ScreenSnappedPoint.Y)local d=math.min(50,math.max(50,(SnappingStartAim-ScreenSnappedPoint).magnitude*1.5))if SnappingStage=="\068\105\114\101\099\116\105\111\110"then local A=math.deg(math.atan2(SnappingEndAim.Y-ScreenSnappedPoint.Y,SnappingEndAim.X-ScreenSnappedPoint.X))A=A>0 and A-360 or A local n={}for d,C in pairs(SnappingStartDirections)do local a=Workspace.CurrentCamera:WorldToScreenPoint(C.Offset)local V=math.deg(math.atan2(a.Y-ScreenSnappedPoint.Y,a.X-ScreenSnappedPoint.X))V=V>0 and V-360 or V local K=math.abs(A-V)%180 K=K>90 and 180-K or K table.insert(n,{Face=C.Face;AngleDelta=K;DirectionAngle=V,Offset=C.Offset})end table.sort(n,function(A,d)return A.AngleDelta<d.AngleDelta end)DirectionLine.Position=UDim2.new(0,ScreenSnappedPoint.X,0,ScreenSnappedPoint.Y)if math.abs(A-n[1].DirectionAngle)<=90 then DirectionLine.Rotation=n[1].DirectionAngle else DirectionLine.Rotation=180+n[1].DirectionAngle end DirectionLine.PointMarker.Rotation=-DirectionLine.Rotation DirectionLine.SnapProgress.Size=UDim2.new(0,d,2,0)DirectionLine.Visible=true local C=(SnappingEndAim-ScreenSnappedPoint).magnitude if C<d then return end DirectionLine:Destroy()SnappingDirection=n[1].Face SnappingDirectionOffset=n[1].Offset SnappingStage="\068\101\115\116\105\110\097\116\105\111\110"SnapTracking.TrackEdgeMidpoints=true SnapTracking.TrackFaceCentroids=true SnapTracking.TargetFilter=function(A)return not A.Locked end SnapTracking.TargetBlacklist=Selection.Items AlignmentLine=Core.Tool.Interfaces.SnapLineSegment:Clone()AlignmentLine.Visible=false AlignmentLine.Parent=Core.UI SnapTracking.StartTracking(function(A)if A and A.p~=SnappedPoint then SnappedPoint=A.p PointSnapped:Fire(A.p)end end)end end)Connections.Snap=PointSnapped:Connect(function(A)if SnappingStage=="\068\101\115\116\105\110\097\116\105\111\110"then local d=(SnappingDirectionOffset-SnappingStartPoint).unit local n=(A-SnappingStartPoint):Dot(d)local C=ResizePartsByFace(SnappingDirection,n,"\078\111\114\109\097\108",SnappingStartSelectionState)if C then if ResizeTool.UI then ResizeTool.UI.Changes.Text.Text="\114\101\115\105\122\101\100 "..(Support.Round(n,3).." \115\116\117\100\115")end local C=Workspace.CurrentCamera:WorldToScreenPoint(SnappingStartPoint+d*n)C=Vector2.new(C.X,C.Y)local a=Workspace.CurrentCamera:WorldToScreenPoint(A)a=Vector2.new(a.X,a.Y)local V=math.deg(math.atan2(a.Y-C.Y,a.X-C.X))local K=C:Lerp(a,.5)AlignmentLine.Position=UDim2.new(0,K.X,0,K.Y)AlignmentLine.Rotation=V AlignmentLine.Size=UDim2.new(0,(a-C).magnitude,0,1)AlignmentLine.PointMarkerA.Rotation=-V AlignmentLine.Visible=true end if Core.Mode=="\084\111\111\108"and Security.ArePartsViolatingAreas(Selection.Parts,Core.Player,false,AreaPermissions)then for A,d in pairs(SnappingStartSelectionState)do A.Size=d.Size A.CFrame=d.CFrame end end end end)end)end
-- Event that fires when a new point is snapped
PointSnapped = Signal.new()

function StartSnapping()
	-- Make sure snapping isn't already enabled
	if SnappingStage or SnapTracking.Enabled then
		return
	end

	-- Start first snapping stage
	SnappingStage = "Starting"

	-- Only enable corner snapping
	SnapTracking.TrackEdgeMidpoints = false
	SnapTracking.TrackFaceCentroids = false
	SnapTracking.TargetFilter = function(Target)
		return Selection.PartIndex[Target]
	end

	-- Trigger the PointSnapped event when a new point is snapped
	SnapTracking.StartTracking(function(NewPoint)
		if NewPoint and NewPoint.p ~= SnappedPoint then
			SnappedPoint = NewPoint.p
			PointSnapped:Fire(NewPoint.p)
		end
	end)

	-- Listen for when the user starts dragging while in snap mode
	Connections.SnapDragStart = Support.AddUserInputListener("Began", "MouseButton1", false, function(Input)
		-- Initialize snapping state
		SnappingStage = "Direction"
		SnappingStartAim = Vector2.new(Input.Position.X, Input.Position.Y)
		SnappingStartPoint = SnappedPoint
		SnappingStartTarget = SnapTracking.Target
		SnappingStartDirections = GetFaceOffsetsFromCorner(SnappingStartTarget, SnappingStartPoint)
		SnappingStartSelectionState = PreparePartsForResizing()
		AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Selection.Parts), Core.Player)

		-- Pause snapping
		SnapTracking.StopTracking()

		-- Start a direction line
		DirectionLine = Core.Tool.Interfaces.SnapLine:Clone()
		DirectionLine.Parent = Core.UI
		DirectionLine.Visible = false

		-- Track changes for history
		TrackChange()

		-- Listen for when the user drags
		Connections.SnapDrag = Support.AddUserInputListener("Changed", "MouseMovement", true, function(Input)
			-- Update the latest aim
			SnappingEndAim = Vector2.new(Input.Position.X, Input.Position.Y)
			ScreenSnappedPoint = Workspace.CurrentCamera:WorldToScreenPoint(SnappingStartPoint)
			ScreenSnappedPoint = Vector2.new(ScreenSnappedPoint.X, ScreenSnappedPoint.Y)

			-- Calculate direction setting length
			local DirectionSettingLength =
				math.min(50, math.max(50, (SnappingStartAim - ScreenSnappedPoint).magnitude * 1.5))

			-- Use the mouse position to figure out the resize direction (until after direction setting length)
			if SnappingStage == "Direction" then
				-- Get current angle from snap point
				local DragAngle = math.deg(
					math.atan2(SnappingEndAim.Y - ScreenSnappedPoint.Y, SnappingEndAim.X - ScreenSnappedPoint.X)
				)
				DragAngle = (DragAngle > 0) and (DragAngle - 360) or DragAngle

				-- Go through corner offsets representing the possible directions
				local Directions = {}
				for _, Direction in pairs(SnappingStartDirections) do
					-- Map the corner & corner offset to screen points
					local ScreenOffsetPoint = Workspace.CurrentCamera:WorldToScreenPoint(Direction.Offset)

					-- Get direction angle from snap point
					local DirectionAngle = math.deg(
						math.atan2(
							ScreenOffsetPoint.Y - ScreenSnappedPoint.Y,
							ScreenOffsetPoint.X - ScreenSnappedPoint.X
						)
					)
					DirectionAngle = (DirectionAngle > 0) and (DirectionAngle - 360) or DirectionAngle

					-- Calculate delta between drag and direction angles
					local AngleDelta = math.abs(DragAngle - DirectionAngle) % 180
					AngleDelta = (AngleDelta > 90) and (180 - AngleDelta) or AngleDelta

					-- Insert the potential direction
					table.insert(Directions, {
						Face = Direction.Face,
						AngleDelta = AngleDelta,
						DirectionAngle = DirectionAngle,
						Offset = Direction.Offset,
					})
				end

				-- Get the direction most similar to the dragging angle
				table.sort(Directions, function(A, B)
					return A.AngleDelta < B.AngleDelta
				end)

				-- Center direction line at snap point
				DirectionLine.Position = UDim2.new(0, ScreenSnappedPoint.X, 0, ScreenSnappedPoint.Y)

				-- Orient direction line towards drag direction
				if math.abs(DragAngle - Directions[1].DirectionAngle) <= 90 then
					DirectionLine.Rotation = Directions[1].DirectionAngle
				else
					DirectionLine.Rotation = 180 + Directions[1].DirectionAngle
				end

				-- Show the direction line
				DirectionLine.PointMarker.Rotation = -DirectionLine.Rotation
				DirectionLine.SnapProgress.Size = UDim2.new(0, DirectionSettingLength, 2, 0)
				DirectionLine.Visible = true

				-- Check if drag has passed direction setting length
				local Length = (SnappingEndAim - ScreenSnappedPoint).magnitude
				if Length < DirectionSettingLength then
					return
				end

				-- Clear the direction line
				DirectionLine:Destroy()

				-- Select the resizing direction that was closest to the mouse drag
				SnappingDirection = Directions[1].Face
				SnappingDirectionOffset = Directions[1].Offset

				-- Move to the destination-picking stage of snapping
				SnappingStage = "Destination"

				-- Set destination-stage snapping options
				SnapTracking.TrackEdgeMidpoints = true
				SnapTracking.TrackFaceCentroids = true
				SnapTracking.TargetFilter = function(Target)
					return not Target.Locked
				end
				SnapTracking.TargetBlacklist = Selection.Items

				-- Start a distance alignment line
				AlignmentLine = Core.Tool.Interfaces.SnapLineSegment:Clone()
				AlignmentLine.Visible = false
				AlignmentLine.Parent = Core.UI

				-- Re-enable snapping to select destination
				SnapTracking.StartTracking(function(NewPoint)
					if NewPoint and NewPoint.p ~= SnappedPoint then
						SnappedPoint = NewPoint.p
						PointSnapped:Fire(NewPoint.p)
					end
				end)
			end
		end)

		-- Listen for when a new point is snapped
		Connections.Snap = PointSnapped:Connect(function(SnappedPoint)
			-- Resize to snap point if in the destination stage of snapping
			if SnappingStage == "Destination" then
				-- Calculate direction and distance to resize towards
				local Direction = (SnappingDirectionOffset - SnappingStartPoint).unit
				local Distance = (SnappedPoint - SnappingStartPoint):Dot(Direction)

				-- Resize the parts on the selected faces by the calculated distance
				local Success = ResizePartsByFace(SnappingDirection, Distance, "Normal", SnappingStartSelectionState)

				-- Update the UI on resize success
				if Success then
					-- Update "studs resized" indicator
					if ResizeTool.UI then
						ResizeTool.UI.Changes.Text.Text = "resized " .. Support.Round(Distance, 3) .. " studs"
					end

					-- Get snap point and destination point screen positions for UI alignment
					local ScreenStartPoint =
						Workspace.CurrentCamera:WorldToScreenPoint(SnappingStartPoint + (Direction * Distance))
					ScreenStartPoint = Vector2.new(ScreenStartPoint.X, ScreenStartPoint.Y)
					local ScreenDestinationPoint = Workspace.CurrentCamera:WorldToScreenPoint(SnappedPoint)
					ScreenDestinationPoint = Vector2.new(ScreenDestinationPoint.X, ScreenDestinationPoint.Y)

					-- Update the distance alignment line
					local AlignmentAngle = math.deg(
						math.atan2(
							ScreenDestinationPoint.Y - ScreenStartPoint.Y,
							ScreenDestinationPoint.X - ScreenStartPoint.X
						)
					)
					local AlignmentCenter = ScreenStartPoint:Lerp(ScreenDestinationPoint, 0.5)
					AlignmentLine.Position = UDim2.new(0, AlignmentCenter.X, 0, AlignmentCenter.Y)
					AlignmentLine.Rotation = AlignmentAngle
					AlignmentLine.Size = UDim2.new(0, (ScreenDestinationPoint - ScreenStartPoint).magnitude, 0, 1)
					AlignmentLine.PointMarkerA.Rotation = -AlignmentAngle
					AlignmentLine.Visible = true
				end

				-- Make sure we're not entering any unauthorized private areas
				if
					Core.Mode == "Tool"
					and Security.ArePartsViolatingAreas(Selection.Parts, Core.Player, false, AreaPermissions)
				then
					for Part, State in pairs(SnappingStartSelectionState) do
						Part.Size = State.Size
						Part.CFrame = State.CFrame
					end
				end
			end
		end)
	end)
end
