PointSnapped=Core.RbxUtility.CreateSignal()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=Selection.IsSelected SnapTracking.StartTracking(function(g)if g and g.p~=SnappedPoint then SnappedPoint=g.p PointSnapped:fire(g.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(g)SnappingStage="\068\105\114\101\099\116\105\111\110"SnappingStartAim=Vector2.new(g.Position.X,g.Position.Y)SnappingStartPoint=SnappedPoint SnappingStartTarget=SnapTracking.Target SnappingStartDirections=GetFaceOffsetsFromCorner(SnappingStartTarget,SnappingStartPoint)SnappingStartSelectionState=PreparePartsForResizing()AreaPermissions=Security.GetPermissions(Security.GetSelectionAreas(Selection.Items),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(g)SnappingEndAim=Vector2.new(g.Position.X,g.Position.Y)ScreenSnappedPoint=Workspace.CurrentCamera:WorldToScreenPoint(SnappingStartPoint)ScreenSnappedPoint=Vector2.new(ScreenSnappedPoint.X,ScreenSnappedPoint.Y)local G=math.min(50,math.max(50,(SnappingStartAim-ScreenSnappedPoint).magnitude*1.5))if SnappingStage=="\068\105\114\101\099\116\105\111\110"then local g=math.deg(math.atan2(SnappingEndAim.Y-ScreenSnappedPoint.Y,SnappingEndAim.X-ScreenSnappedPoint.X))g=g>0 and g-360 or g local F={}for G,C in pairs(SnappingStartDirections)do local a=Workspace.CurrentCamera:WorldToScreenPoint(C.Offset)local N=math.deg(math.atan2(a.Y-ScreenSnappedPoint.Y,a.X-ScreenSnappedPoint.X))N=N>0 and N-360 or N local u=math.abs(g-N)%180 u=u>90 and 180-u or u table.insert(F,{Face=C.Face;AngleDelta=u,DirectionAngle=N,Offset=C.Offset})end table.sort(F,function(g,G)return g.AngleDelta<G.AngleDelta end)DirectionLine.Position=UDim2.new(0,ScreenSnappedPoint.X,0,ScreenSnappedPoint.Y)if math.abs(g-F[1].DirectionAngle)<=90 then DirectionLine.Rotation=F[1].DirectionAngle else DirectionLine.Rotation=180+F[1].DirectionAngle end DirectionLine.PointMarker.Rotation=-DirectionLine.Rotation DirectionLine.SnapProgress.Size=UDim2.new(0,G,2,0)DirectionLine.Visible=true local C=(SnappingEndAim-ScreenSnappedPoint).magnitude if C<G then return end DirectionLine:Destroy()SnappingDirection=F[1].Face SnappingDirectionOffset=F[1].Offset SnappingStage="\068\101\115\116\105\110\097\116\105\111\110"SnapTracking.TrackEdgeMidpoints=true SnapTracking.TrackFaceCentroids=true SnapTracking.TargetFilter=function(g)return not g.Locked end SnapTracking.TargetBlacklist=Selection.Items AlignmentLine=Core.Tool.Interfaces.SnapLineSegment:Clone()AlignmentLine.Visible=false AlignmentLine.Parent=Core.UI SnapTracking.StartTracking(function(g)if g and g.p~=SnappedPoint then SnappedPoint=g.p PointSnapped:fire(g.p)end end)end end)Connections.Snap=PointSnapped:connect(function(g)if SnappingStage=="\068\101\115\116\105\110\097\116\105\111\110"then local G=(SnappingDirectionOffset-SnappingStartPoint).unit local F=(g-SnappingStartPoint):Dot(G)local C=ResizePartsByFace(SnappingDirection,F,"\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(F,3).." \115\116\117\100\115")end local C=Workspace.CurrentCamera:WorldToScreenPoint(SnappingStartPoint+G*F)C=Vector2.new(C.X,C.Y)local a=Workspace.CurrentCamera:WorldToScreenPoint(g)a=Vector2.new(a.X,a.Y)local N=math.deg(math.atan2(a.Y-C.Y,a.X-C.X))local u=C:Lerp(a,.5)AlignmentLine.Position=UDim2.new(0,u.X,0,u.Y)AlignmentLine.Rotation=N AlignmentLine.Size=UDim2.new(0,(a-C).magnitude,0,1)AlignmentLine.PointMarkerA.Rotation=-N AlignmentLine.Visible=true end if Core.Mode=="\084\111\111\108"and Security.ArePartsViolatingAreas(Selection.Items,Core.Player,false,AreaPermissions)then for g,G in pairs(SnappingStartSelectionState)do g.Size=G.Size g.CFrame=G.CFrame end end end end)end)end

PointSnapped = Core.RbxUtility.CreateSignal();

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 = Selection.IsSelected;

	-- 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.Items), 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.Items, 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;
