ArcHandles.CircleSlices=60 function ArcHandles.new(g)local r=setmetatable({},ArcHandles)r.Maid=Maid.new()local J=Instance.new("\083\099\114\101\101\110\071\117\105")r.Gui=J J.Name="\066\084\065\114\099\072\097\110\100\108\101\115"J.IgnoreGuiInset=true r.Maid.Gui=J r.IsMouseAvailable=UserInputService.MouseEnabled r:CreateCircles()r:CreateHandles(g)r.Camera=Workspace.CurrentCamera r.GuiInset=GuiService:GetGuiInset()r.ObstacleBlacklistIndex=Support.FlipTable(g.ObstacleBlacklist or{})r.ObstacleBlacklist=Support.Keys(r.ObstacleBlacklistIndex)r:SetAdornee(g.Adornee)r.Gui.Parent=g.Parent return r end function ArcHandles.CreateCircles(g)local r=Instance.new("\070\111\108\100\101\114")r.Name="\065\120\105\115\067\105\114\099\108\101\115"r.Parent=g.Gui local J={}g.AxisCircles=J local Q=(2*math.pi)/g.CircleSlices for Q,O in ipairs(Enum.Axis:GetEnumItems())do local p=g.AxisColors[O.Name]local H=Instance.new("\070\111\108\100\101\114",r)H.Name=O.Name local d={}J[O.Name]=d for g=1,g.CircleSlices,1 do local r=Instance.new("\067\121\108\105\110\100\101\114\072\097\110\100\108\101\065\100\111\114\110\109\101\110\116")r.Transparency=.4 r.Color3=p r.Radius=0 r.Height=0 r.Parent=H d[g]=r end end end function ArcHandles.CreateHandles(r,g)local J=Instance.new("\070\111\108\100\101\114")J.Name="\072\097\110\100\108\101\115"J.Parent=r.Gui r.Handles={}r.HandleStates={}for Q,O in ipairs(Enum.NormalId:GetEnumItems())do local p=r.SideToAxis[O.Name]local H=r.AxisColors[p]local d=Instance.new("\073\109\097\103\101\066\117\116\116\111\110")d.Name=O.Name d.Image="\114\098\120\097\115\115\101\116\105\100\058\047\047\050\051\052\055\049\052\053\048\049\050"d.ImageColor3=H d.ImageTransparency=.33 d.AnchorPoint=Vector2.new(.5,.5)d.BackgroundTransparency=1 d.BorderSizePixel=0 d.ZIndex=1 d.Visible=false local D=d:Clone()D.Active=false D.Size=UDim2.new(0,4,0,4)D.Position=UDim2.new(.5,0,.5,0)D.Visible=true D.Parent=d D.ZIndex=0 local V=Maid.new()r.Maid[O.Name]=V V.HoverStart=d.MouseEnter:Connect(function()d.ImageTransparency=0 r:SetCircleTransparency(p,0)end)V.HoverEnd=d.MouseLeave:Connect(function()d.ImageTransparency=.33 r:SetCircleTransparency(p,.4)end)V.DragStart=d.MouseButton1Down:Connect(function(J,Q)local O=r.HandleStates[d].PlaneNormal local H=r.HandleStates[d].HandleCFrame local D=r.HandleStates[d].AdorneeCFrame local b=r.Camera:ViewportPointToRay(J,Q)local W=(H.p-b.Origin):Dot(O)/b.Direction:Dot(O)local x=W*b.Direction+b.Origin local I=D:PointToObjectSpace(x)if g.OnDragStart then g.OnDragStart()end local function y(J)local Q=r.Camera:ScreenPointToRay(J.X,J.Y)local d=(H.p-Q.Origin):Dot(O)/Q.Direction:Dot(O)local V=d*Q.Direction+Q.Origin local b=D:PointToObjectSpace(V)local W if p=="\088"then local g=math.atan2(I.Y,-I.Z)W=math.atan2(b.Y,-b.Z)-g elseif p=="\089"then local g=math.atan2(I.X,I.Z)W=math.atan2(b.X,b.Z)-g elseif p=="\090"then local g=math.atan2(I.X,I.Y)W=math.atan2(-b.X,b.Y)-g end if g.OnDrag then g.OnDrag(p,W)end end local c=Maid.new()V.Dragging=c c.Drag=Support.AddUserInputListener("\067\104\097\110\103\101\100",{"\077\111\117\115\101\077\111\118\101\109\101\110\116";"\084\111\117\099\104"},true,function(g)y(g.Position)end)c.InHandleDrag=d.MouseMoved:Connect(function(g,J)local Q=Vector2.new(g,J)-r.GuiInset y(Q)end)c.DragEnd=Support.AddUserInputListener("\069\110\100\101\100",{"\077\111\117\115\101\066\117\116\116\111\110\049","\084\111\117\099\104"},true,function(g)V.Dragging=nil end)c.Callback=function()(coroutine.wrap(g.OnDragEnd))()end end)V.InHandleDragEnd=d.MouseButton1Up:Connect(function()V.Dragging=nil end)d.Parent=J r.Handles[O.Name]=d end end function ArcHandles.Hide(g)if not g.Running then return g end g:Pause()g.Gui.Enabled=false end function ArcHandles.Pause(g)g.Running=false end local function g(g)return(g.CFrame.p-g.Focus.p).magnitude<=.6 end function ArcHandles.Resume(r)if r.Running then return r end r.Running=true for g,J in pairs(r.Handles)do(coroutine.wrap(function()while r.Running do r:UpdateHandle(g,J)RunService.RenderStepped:Wait()end end))()end for g,J in pairs(r.AxisCircles)do(coroutine.wrap(function()while r.Running do r:UpdateCircle(g,J)RunService.RenderStepped:Wait()end end))()end if Players.LocalPlayer then(coroutine.wrap(function()while r.Running do local J=g(r.Camera)local Q=Players.LocalPlayer.Character if Q then r.ObstacleBlacklistIndex[Q]=J and true or nil r.ObstacleBlacklist=Support.Keys(r.ObstacleBlacklistIndex)end wait(.2)end end))()end r.Gui.Enabled=true end function ArcHandles.SetAdornee(r,g)r.Adornee=g r.IsAdorneeModel=g and g:IsA("\077\111\100\101\108")or nil for r,J in pairs(r.AxisCircles)do for r,J in ipairs(J)do J.Adornee=g end end if g then r:Resume()else r:Hide()end return r end function ArcHandles.SetCircleTransparency(J,g,r)for g,J in ipairs(J.AxisCircles[g])do J.Transparency=r end end local function r(g,r)local J,Q=g:WorldToViewportPoint(r)local O=J.Z J=Vector2.new(J.X,J.Y)if O<0 then J=g.ViewportSize-J end return J,O,Q end function ArcHandles.BlacklistObstacle(r,g)if g then r.ObstacleBlacklistIndex[g]=true r.ObstacleBlacklist=Support.Keys(r.ObstacleBlacklistIndex)end end function ArcHandles.UpdateHandle(Q,g,J)local O=Q.Camera if not Q.Adornee then J.Visible=false return end local p=Q.IsAdorneeModel and Q.Adornee:GetModelCFrame()or Q.Adornee.CFrame local H=Q.IsAdorneeModel and Q.Adornee:GetModelSize()or Q.Adornee.Size local d,D,V=r(O,p.p)local b=(2*math.tan(math.rad(O.FieldOfView)/2))*D local W=b/O.ViewportSize.X local x=math.max(1,W*14)*(Q.IsMouseAvailable and 1 or 1.6)local I=H.magnitude/2 local y=I+2*x local c=Vector3.FromNormalId(g)local i=p*CFrame.new(y*c)local a=p*Vector3.FromAxis(Q.SideToAxis[g])local E=(a-p.p).unit local U,X,K=r(O,i.p)J.Visible=K local o=(2*math.tan(math.rad(O.FieldOfView)/2))*X local S=O.ViewportSize.X/o local F=math.max(12,.5*S)*(Q.IsMouseAvailable and 1 or 1.6)J.Size=UDim2.new(0,F,0,F)J.Position=UDim2.new(0,U.X,0,U.Y)local s=Q.HandleStates[J]or{}Q.HandleStates[J]=s s.HandleCFrame=i s.PlaneNormal=E s.AdorneeCFrame=p local T=O:ViewportPointToRay(U.X,U.Y)local R=Ray.new(T.Origin,T.Direction*(X-.25))local w,A=Workspace:FindPartOnRayWithIgnoreList(R,Q.ObstacleBlacklist)if w then J.ImageTransparency=1 elseif J.ImageTransparency==1 then J.ImageTransparency=.33 end end function ArcHandles.UpdateCircle(Q,g,J)local O=Q.Camera local p=Q.IsAdorneeModel and Q.Adornee:GetModelCFrame()or Q.Adornee.CFrame local H=Q.IsAdorneeModel and Q.Adornee:GetModelSize()or Q.Adornee.Size local d=Vector3.FromAxis(g)local D=Vector3.FromNormalId(Q.AxisToSide[g])local V,b,W=r(O,p.p)local x=(2*math.tan(math.rad(O.FieldOfView)/2))*b local I=x/O.ViewportSize.X local y=math.max(1,I*14)*(Q.IsMouseAvailable and 1 or 1.6)local c=H.magnitude/2 local i=c+2*y local a=(2*math.pi)/#J local E=1.5 for g,J in ipairs(J)do local Q=CFrame.fromAxisAngle(d,a*(g-1))*(D*i)local H=CFrame.fromAxisAngle(d,a*g)*(D*i)local V=Q:Lerp(H,.5)local b,W,x=r(O,p*V)local I=(2*math.tan(math.rad(O.FieldOfView)/2))*W local y=I/O.ViewportSize.X J.Radius=(E*y)/2 J.CFrame=CFrame.new(V,H)*CFrame.new(0,0,J.Radius/2)J.Height=(H-Q).magnitude end end function ArcHandles.Destroy(g)g.Running=nil g.Maid:Destroy()end return ArcHandles

ArcHandles.CircleSlices = 60

function ArcHandles.new(Options)
    local self = setmetatable({}, ArcHandles)

    -- Create maid for cleanup on destroyal
    self.Maid = Maid.new()

    -- Create UI container
    local Gui = Instance.new('ScreenGui')
    self.Gui = Gui
    Gui.Name = 'BTArcHandles'
    Gui.IgnoreGuiInset = true
    self.Maid.Gui = Gui

    -- Create interface
    self.IsMouseAvailable = UserInputService.MouseEnabled
    self:CreateCircles()
    self:CreateHandles(Options)

    -- Get camera and viewport information
    self.Camera = Workspace.CurrentCamera
    self.GuiInset = GuiService:GetGuiInset()

    -- Get list of ignorable handle obstacles
    self.ObstacleBlacklistIndex = Support.FlipTable(Options.ObstacleBlacklist or {})
    self.ObstacleBlacklist = Support.Keys(self.ObstacleBlacklistIndex)

    -- Enable handles
    self:SetAdornee(Options.Adornee)
    self.Gui.Parent = Options.Parent

    -- Return new handles
    return self
end

function ArcHandles:CreateCircles()
    
    -- Create folder to contain circles
    local CircleFolder = Instance.new('Folder')
    CircleFolder.Name = 'AxisCircles'
    CircleFolder.Parent = self.Gui
    local Circles = {}
    self.AxisCircles = Circles

    -- Determine angle for each circle slice
    local CircleSliceAngle = 2 * math.pi / self.CircleSlices

    -- Set up each axis
    for _, Axis in ipairs(Enum.Axis:GetEnumItems()) do
        local AxisColor = self.AxisColors[Axis.Name]

        -- Create container for circle
        local Circle = Instance.new('Folder', CircleFolder)
        Circle.Name = Axis.Name
        local Lines = {}
        Circles[Axis.Name] = Lines

        -- Create lines for circle
        for i = 1, self.CircleSlices do
            local Line = Instance.new 'CylinderHandleAdornment'
            Line.Transparency = 0.4
            Line.Color3 = AxisColor
            Line.Radius = 0
            Line.Height = 0
            Line.Parent = Circle
            Lines[i] = Line
        end
    end

end

function ArcHandles:CreateHandles(Options)

    -- Create folder to contain handles
    local HandlesFolder = Instance.new('Folder')
    HandlesFolder.Name = 'Handles'
    HandlesFolder.Parent = self.Gui
    self.Handles = {}
    self.HandleStates = {}

    -- Generate a handle for each side
    for _, Side in ipairs(Enum.NormalId:GetEnumItems()) do

        -- Get axis information
        local Axis = self.SideToAxis[Side.Name]
        local AxisColor = self.AxisColors[Axis]

        -- Create handle
        local Handle = Instance.new('ImageButton')
        Handle.Name = Side.Name
        Handle.Image = 'rbxassetid://2347145012'
        Handle.ImageColor3 = AxisColor
        Handle.ImageTransparency = 0.33
        Handle.AnchorPoint = Vector2.new(0.5, 0.5)
        Handle.BackgroundTransparency = 1
        Handle.BorderSizePixel = 0
        Handle.ZIndex = 1
        Handle.Visible = false

        -- Create handle dot
        local HandleDot = Handle:Clone()
        HandleDot.Active = false
        HandleDot.Size = UDim2.new(0, 4, 0, 4)
        HandleDot.Position = UDim2.new(0.5, 0, 0.5, 0)
        HandleDot.Visible = true
        HandleDot.Parent = Handle
        HandleDot.ZIndex = 0

        -- Create maid for handle cleanup
        local HandleMaid = Maid.new()
        self.Maid[Side.Name] = HandleMaid

        -- Add handle hover effect
        HandleMaid.HoverStart = Handle.MouseEnter:Connect(function ()
            Handle.ImageTransparency = 0
            self:SetCircleTransparency(Axis, 0)
        end)
        HandleMaid.HoverEnd = Handle.MouseLeave:Connect(function ()
            Handle.ImageTransparency = 0.33
            self:SetCircleTransparency(Axis, 0.4)
        end)

        -- Listen for handle interactions on click
        HandleMaid.DragStart = Handle.MouseButton1Down:Connect(function (X, Y)
            local InitialHandlePlane = self.HandleStates[Handle].PlaneNormal
            local InitialHandleCFrame = self.HandleStates[Handle].HandleCFrame
            local InitialAdorneeCFrame = self.HandleStates[Handle].AdorneeCFrame

            -- Calculate aim offset
            local AimRay = self.Camera:ViewportPointToRay(X, Y)
            local AimDistance = (InitialHandleCFrame.p - AimRay.Origin):Dot(InitialHandlePlane) / AimRay.Direction:Dot(InitialHandlePlane)
            local AimWorldPoint = (AimDistance * AimRay.Direction) + AimRay.Origin
            local InitialDragOffset = InitialAdorneeCFrame:PointToObjectSpace(AimWorldPoint)

            -- Run callback
            if Options.OnDragStart then
                Options.OnDragStart()
            end

            local function ProcessDragChange(AimScreenPoint)

                -- Calculate current aim
                local AimRay = self.Camera:ScreenPointToRay(AimScreenPoint.X, AimScreenPoint.Y)
                local AimDistance = (InitialHandleCFrame.p - AimRay.Origin):Dot(InitialHandlePlane) / AimRay.Direction:Dot(InitialHandlePlane)
                local AimWorldPoint = (AimDistance * AimRay.Direction) + AimRay.Origin
                local CurrentDragOffset = InitialAdorneeCFrame:PointToObjectSpace(AimWorldPoint)

                -- Calculate angle on dragged axis
                local DragAngle
                if Axis == 'X' then
                    local InitialAngle = math.atan2(InitialDragOffset.Y, -InitialDragOffset.Z)
                    DragAngle = math.atan2(CurrentDragOffset.Y, -CurrentDragOffset.Z) - InitialAngle
                elseif Axis == 'Y' then
                    local InitialAngle = math.atan2(InitialDragOffset.X, InitialDragOffset.Z)
                    DragAngle = math.atan2(CurrentDragOffset.X, CurrentDragOffset.Z) - InitialAngle
                elseif Axis == 'Z' then
                    local InitialAngle = math.atan2(InitialDragOffset.X, InitialDragOffset.Y)
                    DragAngle = math.atan2(-CurrentDragOffset.X, CurrentDragOffset.Y) - InitialAngle
                end

                -- Run drag callback
                if Options.OnDrag then
                    Options.OnDrag(Axis, DragAngle)
                end

            end

            -- Create maid for dragging cleanup
            local DragMaid = Maid.new()
            HandleMaid.Dragging = DragMaid

            -- Perform dragging when aiming anywhere (except handle)
            DragMaid.Drag = Support.AddUserInputListener('Changed', {'MouseMovement', 'Touch'}, true, function (Input)
                ProcessDragChange(Input.Position)
            end)

            -- Perform dragging while aiming at handle
            DragMaid.InHandleDrag = Handle.MouseMoved:Connect(function (X, Y)
                local AimScreenPoint = Vector2.new(X, Y) - self.GuiInset
                ProcessDragChange(AimScreenPoint)
            end)

            -- Finish dragging when input ends
            DragMaid.DragEnd = Support.AddUserInputListener('Ended', {'MouseButton1', 'Touch'}, true, function (Input)
                HandleMaid.Dragging = nil
            end)

            -- Fire callback when dragging ends
            DragMaid.Callback = function ()
                coroutine.wrap(Options.OnDragEnd)()
            end

        end)

        -- Finish dragging when input ends while aiming at handle
        HandleMaid.InHandleDragEnd = Handle.MouseButton1Up:Connect(function ()
            HandleMaid.Dragging = nil
        end)

        -- Save handle
        Handle.Parent = HandlesFolder
        self.Handles[Side.Name] = Handle

    end

end

function ArcHandles:Hide()

    -- Make sure handles are enabled
    if not self.Running then
        return self
    end

    -- Pause updating
    self:Pause()

    -- Hide UI
    self.Gui.Enabled = false

end

function ArcHandles:Pause()
    self.Running = false
end

local function IsFirstPerson(Camera)
    return (Camera.CFrame.p - Camera.Focus.p).magnitude <= 0.6
end

function ArcHandles:Resume()

    -- Make sure handles are disabled
    if self.Running then
        return self
    end

    -- Allow handles to run
    self.Running = true

    -- Update each handle
    for Side, Handle in pairs(self.Handles) do
        coroutine.wrap(function ()
            while self.Running do
                self:UpdateHandle(Side, Handle)
                RunService.RenderStepped:Wait()
            end
        end)()
    end

    -- Update each axis circle
    for Axis, Lines in pairs(self.AxisCircles) do
        coroutine.wrap(function ()
            while self.Running do
                self:UpdateCircle(Axis, Lines)
                RunService.RenderStepped:Wait()
            end
        end)()
    end

    -- Ignore character whenever character enters first person
    if Players.LocalPlayer then
        coroutine.wrap(function ()
            while self.Running do
                local FirstPerson = IsFirstPerson(self.Camera)
                local Character = Players.LocalPlayer.Character
                if Character then
                    self.ObstacleBlacklistIndex[Character] = FirstPerson and true or nil
                    self.ObstacleBlacklist = Support.Keys(self.ObstacleBlacklistIndex)
                end
                wait(0.2)
            end
        end)()
    end

    -- Show UI
    self.Gui.Enabled = true

end

function ArcHandles:SetAdornee(Item)
    -- Return self for chaining

    -- Save new adornee
    self.Adornee = Item
    self.IsAdorneeModel = Item and (Item:IsA 'Model') or nil

    -- Attach axis circles to adornee
    for Axis, Lines in pairs(self.AxisCircles) do
        for _, Line in ipairs(Lines) do
            Line.Adornee = Item
        end
    end

    -- Resume handles
    if Item then
        self:Resume()
    else
        self:Hide()
    end

    -- Return handles for chaining
    return self

end

function ArcHandles:SetCircleTransparency(Axis, Transparency)
    for _, Line in ipairs(self.AxisCircles[Axis]) do
        Line.Transparency = Transparency
    end
end

local function WorldToViewportPoint(Camera, Position)

    -- Get viewport position for point
    local ViewportPoint, Visible = Camera:WorldToViewportPoint(Position)
    local CameraDepth = ViewportPoint.Z
    ViewportPoint = Vector2.new(ViewportPoint.X, ViewportPoint.Y)

    -- Adjust position if point is behind camera
    if CameraDepth < 0 then
        ViewportPoint = Camera.ViewportSize - ViewportPoint
    end

    -- Return point and visibility
    return ViewportPoint, CameraDepth, Visible

end

function ArcHandles:BlacklistObstacle(Obstacle)
    if Obstacle then
        self.ObstacleBlacklistIndex[Obstacle] = true
        self.ObstacleBlacklist = Support.Keys(self.ObstacleBlacklistIndex)
    end
end

function ArcHandles:UpdateHandle(Side, Handle)
    local Camera = self.Camera

    -- Hide handles if not attached to an adornee
    if not self.Adornee then
        Handle.Visible = false
        return
    end

    -- Get adornee CFrame and size
    local AdorneeCFrame = self.IsAdorneeModel and
        self.Adornee:GetModelCFrame() or
        self.Adornee.CFrame
    local AdorneeSize = self.IsAdorneeModel and
        self.Adornee:GetModelSize() or
        self.Adornee.Size

    -- Calculate radius of adornee extents
    local ViewportPoint, CameraDepth, Visible = WorldToViewportPoint(Camera, AdorneeCFrame.p)
    local StudWidth = 2 * math.tan(math.rad(Camera.FieldOfView) / 2) * CameraDepth
    local StudsPerPixel = StudWidth / Camera.ViewportSize.X
    local HandlePadding = math.max(1, StudsPerPixel * 14) * (self.IsMouseAvailable and 1 or 1.6)
    local AdorneeRadius = AdorneeSize.magnitude / 2
    local Radius = AdorneeRadius + 2 * HandlePadding

    -- Calculate CFrame of the handle's side
    local SideUnitVector = Vector3.FromNormalId(Side)
    local HandleCFrame = AdorneeCFrame * CFrame.new(Radius * SideUnitVector)
    local AxisCFrame = AdorneeCFrame * Vector3.FromAxis(self.SideToAxis[Side])
    local HandleNormal = (AxisCFrame - AdorneeCFrame.p).unit

    -- Get viewport position of adornee and the side the handle will be on
    local HandleViewportPoint, HandleCameraDepth, HandleVisible = WorldToViewportPoint(Camera, HandleCFrame.p)

    -- Display handle if side is visible to the camera
    Handle.Visible = HandleVisible

    -- Calculate handle size (12 px, or at least 0.5 studs)
    local StudWidth = 2 * math.tan(math.rad(Camera.FieldOfView) / 2) * HandleCameraDepth
    local PixelsPerStud = Camera.ViewportSize.X / StudWidth
    local HandleSize = math.max(12, 0.5 * PixelsPerStud) * (self.IsMouseAvailable and 1 or 1.6)
    Handle.Size = UDim2.new(0, HandleSize, 0, HandleSize)

    -- Calculate where handles will appear on the screen
    Handle.Position = UDim2.new(
        0, HandleViewportPoint.X,
        0, HandleViewportPoint.Y
    )

    -- Save handle position
    local HandleState = self.HandleStates[Handle] or {}
    self.HandleStates[Handle] = HandleState
    HandleState.HandleCFrame = HandleCFrame
    HandleState.PlaneNormal = HandleNormal
    HandleState.AdorneeCFrame = AdorneeCFrame

    -- Hide handles if obscured by a non-blacklisted part
    local HandleRay = Camera:ViewportPointToRay(HandleViewportPoint.X, HandleViewportPoint.Y)
    local TargetRay = Ray.new(HandleRay.Origin, HandleRay.Direction * (HandleCameraDepth - 0.25))
    local Target, TargetPoint = Workspace:FindPartOnRayWithIgnoreList(TargetRay, self.ObstacleBlacklist)
    if Target then
        Handle.ImageTransparency = 1
    elseif Handle.ImageTransparency == 1 then
        Handle.ImageTransparency = 0.33
    end
end

function ArcHandles:UpdateCircle(Axis, Lines)
    local Camera = self.Camera

    -- Get adornee CFrame and size
    local AdorneeCFrame = self.IsAdorneeModel and
        self.Adornee:GetModelCFrame() or
        self.Adornee.CFrame
    local AdorneeSize = self.IsAdorneeModel and
        self.Adornee:GetModelSize() or
        self.Adornee.Size

    -- Get circle information
    local AxisVector = Vector3.FromAxis(Axis)
    local CircleVector = Vector3.FromNormalId(self.AxisToSide[Axis])

    -- Determine circle radius
    local ViewportPoint, CameraDepth, Visible = WorldToViewportPoint(Camera, AdorneeCFrame.p)
    local StudWidth = 2 * math.tan(math.rad(Camera.FieldOfView) / 2) * CameraDepth
    local StudsPerPixel = StudWidth / Camera.ViewportSize.X
    local HandlePadding = math.max(1, StudsPerPixel * 14) * (self.IsMouseAvailable and 1 or 1.6)
    local AdorneeRadius = AdorneeSize.magnitude / 2
    local Radius = AdorneeRadius + 2 * HandlePadding

    -- Determine angle of each circle slice
    local Angle = 2 * math.pi / #Lines

    -- Circle thickness (px)
    local Thickness = 1.5

    -- Redraw lines for circle
    for i, Line in ipairs(Lines) do

        -- Calculate arc's endpoints
        local From = CFrame.fromAxisAngle(AxisVector, Angle * (i - 1)) *
            (CircleVector * Radius)
        local To = CFrame.fromAxisAngle(AxisVector, Angle * i) *
            (CircleVector * Radius)
        local Center = From:Lerp(To, 0.5)

        -- Determine thickness of line (in studs)
        local ViewportPoint, CameraDepth, Visible = WorldToViewportPoint(Camera, AdorneeCFrame * Center)
        local StudWidth = 2 * math.tan(math.rad(Camera.FieldOfView) / 2) * CameraDepth
        local StudsPerPixel = StudWidth / Camera.ViewportSize.X
        Line.Radius = Thickness * StudsPerPixel / 2

        -- Position line between the endpoints
        Line.CFrame = CFrame.new(Center, To) *
            CFrame.new(0, 0, Line.Radius / 2)

        -- Make line span between endpoints
        Line.Height = (To - From).magnitude

    end
end

function ArcHandles:Destroy()

    -- Pause updating
    self.Running = nil

    -- Clean up resources
    self.Maid:Destroy()

end

return ArcHandles
