local q=true local K=false pcall(function()local q=(Instance.new("\080\097\114\116")):GetRenderCFrame()K=q~=nil end)local function k(q)return K and q:GetRenderCFrame()or q.CFrame end local function B()local K={}K.ShiftLock=false K.Enabled=false local B=20 local f=false local H=false K.RotateInput=Vector2.new()function K.GetShiftLock(q)return ShiftLockController:IsShiftLocked()end function K.GetHumanoid(q)local K=PlayersService.LocalPlayer return findPlayerHumanoid(K)end function K.GetHumanoidRootPart(q)local k=K:GetHumanoid()return k and k.Torso end function K.GetRenderCFrame(K,q)k(q)end function K.GetSubjectPosition(q)local K=nil local B=workspace.CurrentCamera local f=B and B.CameraSubject if f then if f:IsA("\086\101\104\105\099\108\101\083\101\097\116")then local q=k(f)K=q.p+q:vectorToWorldSpace(SEAT_OFFSET)elseif f:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")then local q=k(f)K=q.p+SEAT_OFFSET elseif f:IsA("\066\097\115\101\080\097\114\116")then local q=k(f)K=q.p elseif f:IsA("\077\111\100\101\108")then K=(f:GetModelCFrame()).p elseif f:IsA("\072\117\109\097\110\111\105\100")then local q=f.Torso if q and q:IsA("\066\097\115\101\080\097\114\116")then local B=k(q)K=B.p+B:vectorToWorldSpace(HEAD_OFFSET+f.CameraOffset)end end end return K end function K.ResetCameraLook(q) end function K.GetCameraLook(q)return workspace.CurrentCamera and workspace.CurrentCamera.CoordinateFrame.lookVector or Vector3.new(0,0,1)end function K.GetCameraZoom(q)if K.currentZoom==nil then local q=PlayersService.LocalPlayer K.currentZoom=q and clamp(q.CameraMinZoomDistance,q.CameraMaxZoomDistance,10)or 10 end return K.currentZoom end function K.GetCameraActualZoom(q)local K=workspace.CurrentCamera if K then return(K.CoordinateFrame.p-K.Focus.p).magnitude end end function K.ViewSizeX(q)local K=1024 local k=PlayersService.LocalPlayer local B=k and k:GetMouse()if B then K=B.ViewSizeX end return K end function K.ViewSizeY(q)local K=768 local k=PlayersService.LocalPlayer local B=k and k:GetMouse()if B then K=B.ViewSizeY end return K end function K.ScreenTranslationToAngle(k,q)local B=K:ViewSizeX()local f=K:ViewSizeY()local H=q.x/B local z=q.y/f return Vector2.new(H,z)end function K.MouseTranslationToAngle(K,q)local k=q.x/1920 local B=q.y/1200 return Vector2.new(k,B)end function K.RotateCamera(k,q,K)local B=CFrame.new(Vector3.new(),q)local f=math.asin(q.y)local H=clamp(-MAX_Y+f,-MIN_Y+f,K.y)local z=((CFrame.Angles(0,-K.x,0)*B)*CFrame.Angles(-H,0,0)).lookVector return z,Vector2.new(K.x,H)end function K.IsInFirstPerson(q)return f end function K.UpdateMouseBehavior(q)if f or q:GetShiftLock()then if UserInputService.MouseBehavior~=Enum.MouseBehavior.LockCenter then pcall(function()GameSettings.RotationType=Enum.RotationType.CameraRelative end)UserInputService.MouseBehavior=Enum.MouseBehavior.LockCenter end else pcall(function()GameSettings.RotationType=Enum.RotationType.MovementRelative end)if H then UserInputService.MouseBehavior=Enum.MouseBehavior.LockCurrentPosition else UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end end function K.ZoomCamera(k,q)local B=PlayersService.LocalPlayer if B then if B.CameraMode==Enum.CameraMode.LockFirstPerson then K.currentZoom=0 else K.currentZoom=clamp(B.CameraMinZoomDistance,B.CameraMaxZoomDistance,q)end end f=k:GetCameraZoom()<2 ShiftLockController:SetIsInFirstPerson(f)k:UpdateMouseBehavior()return k:GetCameraZoom()end local function z(q,K,k)local B=K<0 and-1 or 1 local function f(q,K)local k=B*math.max(1,q/3.3+.5)return k end local H=q local z=K local N=f(H,z)local G=H+z*(k/2)local g=z+N*(k/2)local A=f(G,g)local c=H+g*(k/2)local m=z+A*(k/2)local o=f(c,m)local R=H+m*k local b=z+o*k local t=f(R,b)local V=q+(((z+2*g)+2*m)+b)*(k/6)local a=K+(((N+2*A)+2*o)+t)*(k/6)return V,a end function K.ZoomCameraBy(k,q)local B=K:GetCameraActualZoom()if B then B=z(B,q,1)k:ZoomCamera(B)end return k:GetCameraZoom()end function K.ZoomCameraFixedBy(K,q)return K:ZoomCamera(K:GetCameraZoom()+q)end function K.Update(q) end local N=nil local G=nil local g=nil local A={}local c=0 local m=nil local o=nil K.ZoomEnabled=true K.PanEnabled=true K.KeyPanEnabled=true local function R(q,K)A[q]=K if not K then c=c+1 end end local function b(q,k)if A[q]==nil then A[q]=k if not k then c=c+1 end end if c==1 then if A[q]==false then g=g or K:GetCameraLook()N=N or q.Position G=G or N K.UserPanningTheCamera=true local k=q.Position-G if K.PanEnabled then local q=K:ScreenTranslationToAngle(k)*TOUCH_SENSITIVTY K.RotateInput=K.RotateInput+q end G=q.Position end else g=nil N=nil G=nil K.UserPanningTheCamera=false end if c==2 then local q={}for K,k in pairs(A)do if not k then table.insert(q,K)end end if#q==2 then local k=(q[1].Position-q[2].Position).magnitude if m and o then local q=k/math.max(.01,m)local B=clamp(.1,10,q)if K.ZoomEnabled then K:ZoomCamera(o/B)end else m=k o=K:GetCameraActualZoom()end end else m=nil o=nil end end local function t(q,k)if A[q]==false then if c==1 then g=nil N=nil G=nil K.UserPanningTheCamera=false elseif c==2 then m=nil o=nil end end if A[q]~=nil and A[q]==false then c=c-1 end A[q]=nil end local function V(q,k)if k then return end H=true K:UpdateMouseBehavior()g=K:GetCameraLook()N=q.Position G=N K.UserPanningTheCamera=true end local function a(q,k)H=false K:UpdateMouseBehavior()g=nil N=nil G=nil K.UserPanningTheCamera=false end local function p(q,k)if N and(G and g)then local k=G+q.Delta local B=k-N if K.PanEnabled then local k=K:MouseTranslationToAngle(q.Delta)*(MOUSE_SENSITIVITY*sensitivity.Value)K.RotateInput=K.RotateInput+k end G=k elseif K:IsInFirstPerson()or K:GetShiftLock()then if K.PanEnabled then local k=K:MouseTranslationToAngle(q.Delta)*(MOUSE_SENSITIVITY*sensitivity.Value)K.RotateInput=K.RotateInput+k end end end local function w(q,k)if not k then if K.ZoomEnabled then K:ZoomCameraBy(clamp(-1,1,-q.Position.Z)*1.4)end end end local function U(q)return math.floor(q+.5)end local F=math.pi/4 local function n(q,K,k)if q~=Vector3.new(0,0,0)then q=q.unit local B=math.atan2(q.z,q.x)local f=U((math.atan2(q.z,q.x)+K)/k)*k return f-B end return 0 end local function T(q,k)if k then return end if K.ZoomEnabled then if q.KeyCode==Enum.KeyCode.I then K:ZoomCameraBy(-5)elseif q.KeyCode==Enum.KeyCode.O then K:ZoomCameraBy(5)end end if g==nil and K.KeyPanEnabled then if q.KeyCode==Enum.KeyCode.Left then K.TurningLeft=true elseif q.KeyCode==Enum.KeyCode.Right then K.TurningRight=true elseif q.KeyCode==Enum.KeyCode.Comma then local q=n(K:GetCameraLook()*Vector3.new(1,0,1),-F*.75,F)if q~=0 then K.RotateInput=K.RotateInput+Vector2.new(q,0)K.LastUserPanCamera=tick()K.LastCameraTransform=nil end elseif q.KeyCode==Enum.KeyCode.Period then local q=n(K:GetCameraLook()*Vector3.new(1,0,1),F*.75,F)if q~=0 then K.RotateInput=K.RotateInput+Vector2.new(q,0)K.LastUserPanCamera=tick()K.LastCameraTransform=nil end elseif q.KeyCode==Enum.KeyCode.PageUp then K.RotateInput=K.RotateInput+Vector2.new(0,math.rad(15))K.LastCameraTransform=nil elseif q.KeyCode==Enum.KeyCode.PageDown then K.RotateInput=K.RotateInput+Vector2.new(0,math.rad(-15))K.LastCameraTransform=nil end end end local function O(q,k)if q.KeyCode==Enum.KeyCode.Left then K.TurningLeft=false elseif q.KeyCode==Enum.KeyCode.Right then K.TurningRight=false end end local Q=nil local E=.7 local D=0 local M=.1 local J=1.0 local I=Vector2.new(0,0)local u=.65 local v=nil local x=.35 local Y=.8 local function X(q)q=clamp(-1,1,q)if q>=0 then return(x*q)/((x-q)+1)end return-((Y*-q)/((Y+q)+1))end local y=.1 local function r(q)return(1+y)*(2*math.abs(q)-1)-y end local function Z(q)return q/2+.5 end local function C(q)local function K(q)local K=1 if q<0 then K=-1 end local k=Z(X(r(math.abs(q))))k=k*K return clamp(-1,1,k)end return Vector2.new(K(q.x),K(q.y))end function K.UpdateGamepad(q)local k=K.GamepadPanningCamera if k then k=C(k)local q=tick()if k.X~=0 or k.Y~=0 then K.userPanningTheCamera=true elseif k==Vector2.new(0,0)then Q=nil if I==Vector2.new(0,0)then D=0 end end local B=0 if Q then local K=(q-Q)*10 D=D+M*((K*K)/E)if D>M then D=M end if v then local K=(k-I)/(q-Q)local B=(K-v).magnitude if B>12 then D=D*(20/B)if D>M then D=M end end end B=J*D v=(k-I)/(q-Q)end I=k Q=q return Vector2.new(k.X*B,(k.Y*B)*u)end return Vector2.new(0,0)end local j,s,L,W=nil,nil,nil,nil function K.DisconnectInputEvents(q)if j then j:disconnect()j=nil end if s then s:disconnect()s=nil end if L then L:disconnect()L=nil end if W then W:disconnect()W=nil end K.TurningLeft=false K.TurningRight=false K.LastCameraTransform=nil q.LastSubjectCFrame=nil K.UserPanningTheCamera=false K.RotateInput=Vector2.new()K.GamepadPanningCamera=Vector2.new(0,0)N=nil G=nil g=nil H=false A={}c=0 m=nil o=nil if UserInputService.MouseBehavior~=Enum.MouseBehavior.LockCenter then UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end function K.ConnectInputEvents(q)j=UserInputService.InputBegan:connect(function(q,K)if q.UserInputType==Enum.UserInputType.Touch and IsTouch then R(q,K)elseif q.UserInputType==Enum.UserInputType.MouseButton2 and not IsTouch then V(q,K)end if q.UserInputType==Enum.UserInputType.Keyboard then T(q,K)end end)s=UserInputService.InputChanged:connect(function(q,K)if q.UserInputType==Enum.UserInputType.Touch and IsTouch then b(q,K)elseif q.UserInputType==Enum.UserInputType.MouseMovement and not IsTouch then p(q,K)elseif q.UserInputType==Enum.UserInputType.MouseWheel and not IsTouch then w(q,K)end end)L=UserInputService.InputEnded:connect(function(q,K)if q.UserInputType==Enum.UserInputType.Touch and IsTouch then t(q,K)elseif q.UserInputType==Enum.UserInputType.MouseButton2 and not IsTouch then a(q,K)end if q.UserInputType==Enum.UserInputType.Keyboard then O(q,K)end end)W=ShiftLockController.OnShiftLockToggled.Event:connect(function()K:UpdateMouseBehavior()end)K.RotateInput=Vector2.new()local k=function(q,k,B)if B.UserInputType==Enum.UserInputType.Gamepad1 and B.KeyCode==Enum.KeyCode.Thumbstick2 then if k==Enum.UserInputState.Cancel then K.GamepadPanningCamera=Vector2.new(0,0)return end local q=Vector2.new(B.Position.X,-B.Position.Y)if q.magnitude>.1 then K.GamepadPanningCamera=Vector2.new(B.Position.X,-B.Position.Y)else K.GamepadPanningCamera=Vector2.new(0,0)end end end local B=function(q,k,B)if B.UserInputType==Enum.UserInputType.Gamepad1 and(B.KeyCode==Enum.KeyCode.ButtonR3 and k==Enum.UserInputState.Begin)then if K.currentZoom>.5 then K:ZoomCamera(0)else K:ZoomCamera(10)end end end game.ContextActionService:BindAction("\082\111\111\116\067\097\109\071\097\109\101\112\097\100\080\097\110",k,false,Enum.KeyCode.Thumbstick2)game.ContextActionService:BindAction("\082\111\111\116\067\097\109\071\097\109\101\112\097\100\090\111\111\109",B,false,Enum.KeyCode.ButtonR3)q:UpdateMouseBehavior()end function K.SetEnabled(K,q)if q~=K.Enabled then K.Enabled=q if K.Enabled then K:ConnectInputEvents()else K:DisconnectInputEvents()end end end local function l(k)k.Changed:connect(function(q)if K.Enabled then if q=="\067\097\109\101\114\097\077\111\100\101"or q=="\067\097\109\101\114\097\077\097\120\090\111\111\109\068\105\115\116\097\110\099\101"or q=="\067\097\109\101\114\097\077\105\110\090\111\111\109\068\105\115\116\097\110\099\101"then K:ZoomCameraFixedBy(0)end end end)local function B(q)K:ZoomCamera(12.5)local B=findPlayerHumanoid(k)local f=tick()while tick()-f<.3 and(B==nil or B.Torso==nil)do wait()B=findPlayerHumanoid(k)end local function H()if B and(B.Torso and k.Character==q)then local q=(B.Torso.CFrame.lookVector-Vector3.new(0,.23,0)).unit local k=findAngleBetweenXZVectors(q,K:GetCameraLook())local f=math.asin((K:GetCameraLook()).y)-math.asin(q.y)if not IsFinite(k)then k=0 end if not IsFinite(f)then f=0 end K.RotateInput=Vector2.new(k,f)K.LastCameraTransform=nil end end wait()H()end k.CharacterAdded:connect(function(k)if K.Enabled or q then B(k)q=false end end)if k.Character then spawn(function()B(k.Character)end)end end if PlayersService.LocalPlayer then l(PlayersService.LocalPlayer)end PlayersService.ChildAdded:connect(function(q)if q and PlayersService.LocalPlayer==q then l(PlayersService.LocalPlayer)end end)return K end return B

local SetCameraOnSpawn = true


local UseRenderCFrame = false
pcall(function()
	local rc = Instance.new('Part'):GetRenderCFrame()
	UseRenderCFrame = (rc ~= nil)
end)

local function GetRenderCFrame(part)
	return UseRenderCFrame and part:GetRenderCFrame() or part.CFrame
end

local function CreateCamera()
	local this = {}

	this.ShiftLock = false
	this.Enabled = false
	local pinchZoomSpeed = 20
	local isFirstPerson = false
	local isRightMouseDown = false
	this.RotateInput = Vector2.new()

	function this:GetShiftLock()
		return ShiftLockController:IsShiftLocked()
	end

	function this:GetHumanoid()
		local player = PlayersService.LocalPlayer
		return findPlayerHumanoid(player)
	end

	function this:GetHumanoidRootPart()
		local humanoid = this:GetHumanoid()
		return humanoid and humanoid.Torso
	end

	function this:GetRenderCFrame(part)
		GetRenderCFrame(part)
	end

	function this:GetSubjectPosition()
		local result = nil
		local camera = workspace.CurrentCamera
		local cameraSubject = camera and camera.CameraSubject
		if cameraSubject then
			if cameraSubject:IsA('VehicleSeat') then
				local subjectCFrame = GetRenderCFrame(cameraSubject)
				result = subjectCFrame.p + subjectCFrame:vectorToWorldSpace(SEAT_OFFSET)
			elseif cameraSubject:IsA('SkateboardPlatform') then
				local subjectCFrame = GetRenderCFrame(cameraSubject)
				result = subjectCFrame.p + SEAT_OFFSET
			elseif cameraSubject:IsA('BasePart') then
				local subjectCFrame = GetRenderCFrame(cameraSubject)
				result = subjectCFrame.p
			elseif cameraSubject:IsA('Model') then
				result = cameraSubject:GetModelCFrame().p
			elseif cameraSubject:IsA('Humanoid') then
				local humanoidRootPart = cameraSubject.Torso
				if humanoidRootPart and humanoidRootPart:IsA('BasePart') then
					local subjectCFrame = GetRenderCFrame(humanoidRootPart)
					result = subjectCFrame.p +
						subjectCFrame:vectorToWorldSpace(HEAD_OFFSET + cameraSubject.CameraOffset)
				end
			end
		end
		return result
	end

	function this:ResetCameraLook()
	end

	function this:GetCameraLook()
		return workspace.CurrentCamera and workspace.CurrentCamera.CoordinateFrame.lookVector or Vector3.new(0,0,1)
	end

	function this:GetCameraZoom()
		if this.currentZoom == nil then
			local player = PlayersService.LocalPlayer
			this.currentZoom = player and clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, 10) or 10
		end
		return this.currentZoom
	end

	function this:GetCameraActualZoom()
		local camera = workspace.CurrentCamera
		if camera then
			return (camera.CoordinateFrame.p - camera.Focus.p).magnitude
		end
	end

	function this:ViewSizeX()
		local result = 1024
		local player = PlayersService.LocalPlayer
		local mouse = player and player:GetMouse()
		if mouse then
			result = mouse.ViewSizeX
		end
		return result
	end

	function this:ViewSizeY()
		local result = 768
		local player = PlayersService.LocalPlayer
		local mouse = player and player:GetMouse()
		if mouse then
			result = mouse.ViewSizeY
		end
		return result
	end

	function this:ScreenTranslationToAngle(translationVector)
		local screenX = this:ViewSizeX()
		local screenY = this:ViewSizeY()
		local xTheta = (translationVector.x / screenX)
		local yTheta = (translationVector.y / screenY)
		return Vector2.new(xTheta, yTheta)
	end

	function this:MouseTranslationToAngle(translationVector)
		local xTheta = (translationVector.x / 1920)
		local yTheta = (translationVector.y / 1200)
		return Vector2.new(xTheta, yTheta)
	end

	function this:RotateCamera(startLook, xyRotateVector)
		-- Could cache these values so we don't have to recalc them all the time
		local startCFrame = CFrame.new(Vector3.new(), startLook)
		local startVertical = math.asin(startLook.y)
		local yTheta = clamp(-MAX_Y + startVertical, -MIN_Y + startVertical, xyRotateVector.y)
		local resultLookVector = (CFrame.Angles(0, -xyRotateVector.x, 0) * startCFrame * CFrame.Angles(-yTheta,0,0)).lookVector
		return resultLookVector, Vector2.new(xyRotateVector.x, yTheta)
	end

	function this:IsInFirstPerson()
		return isFirstPerson
	end

	-- there are several cases to consider based on the state of input and camera rotation mode
	function this:UpdateMouseBehavior()
		-- first time transition to first person mode or shiftlock
		if isFirstPerson or self:GetShiftLock() then
			if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
				pcall(function() GameSettings.RotationType = Enum.RotationType.CameraRelative end)
				UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
			end
		else
			pcall(function() GameSettings.RotationType = Enum.RotationType.MovementRelative end)
			if isRightMouseDown then
				UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
			else
				UserInputService.MouseBehavior = Enum.MouseBehavior.Default
			end	
		end
	end

	function this:ZoomCamera(desiredZoom)
		local player = PlayersService.LocalPlayer
		if player then
			if player.CameraMode == Enum.CameraMode.LockFirstPerson then
				this.currentZoom = 0
			else
				this.currentZoom = clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, desiredZoom)
			end
		end

		isFirstPerson = self:GetCameraZoom() < 2

		ShiftLockController:SetIsInFirstPerson(isFirstPerson)
		-- set mouse behavior
		self:UpdateMouseBehavior()
		return self:GetCameraZoom()
	end

	local function rk4Integrator(position, velocity, t)
		local direction = velocity < 0 and -1 or 1
		local function acceleration(p, v)
			local accel = direction * math.max(1, (p / 3.3) + 0.5)
			return accel
		end

		local p1 = position
		local v1 = velocity
		local a1 = acceleration(p1, v1)
		local p2 = p1 + v1 * (t / 2)
		local v2 = v1 + a1 * (t / 2)
		local a2 = acceleration(p2, v2)
		local p3 = p1 + v2 * (t / 2)
		local v3 = v1 + a2 * (t / 2)
		local a3 = acceleration(p3, v3)
		local p4 = p1 + v3 * t
		local v4 = v1 + a3 * t
		local a4 = acceleration(p4, v4)

		local positionResult = position + (v1 + 2 * v2 + 2 * v3 + v4) * (t / 6)
		local velocityResult = velocity + (a1 + 2 * a2 + 2 * a3 + a4) * (t / 6)
		return positionResult, velocityResult
	end

	function this:ZoomCameraBy(zoomScale)
		local zoom = this:GetCameraActualZoom()
		if zoom then
			-- Can break into more steps to get more accurate integration
			zoom = rk4Integrator(zoom, zoomScale, 1)
			self:ZoomCamera(zoom)
		end
		return self:GetCameraZoom()
	end

	function this:ZoomCameraFixedBy(zoomIncrement)
		return self:ZoomCamera(self:GetCameraZoom() + zoomIncrement)
	end

	function this:Update()
	end

	---- Input Events ----
	local startPos = nil
	local lastPos = nil
	local panBeginLook = nil

	local fingerTouches = {}
	local NumUnsunkTouches = 0

	local StartingDiff = nil
	local pinchBeginZoom = nil

	this.ZoomEnabled = true
	this.PanEnabled = true
	this.KeyPanEnabled = true

	local function OnTouchBegan(input, processed)
		fingerTouches[input] = processed
		if not processed then
			NumUnsunkTouches = NumUnsunkTouches + 1
		end
	end

	local function OnTouchChanged(input, processed)
		if fingerTouches[input] == nil then
			fingerTouches[input] = processed
			if not processed then
				NumUnsunkTouches = NumUnsunkTouches + 1
			end
		end

		if NumUnsunkTouches == 1 then
			if fingerTouches[input] == false then
				panBeginLook = panBeginLook or this:GetCameraLook()
				startPos = startPos or input.Position
				lastPos = lastPos or startPos
				this.UserPanningTheCamera = true

				local delta = input.Position - lastPos
				if this.PanEnabled then
					local desiredXYVector = this:ScreenTranslationToAngle(delta) * TOUCH_SENSITIVTY
					this.RotateInput = this.RotateInput + desiredXYVector
				end

				lastPos = input.Position
			end
		else
			panBeginLook = nil
			startPos = nil
			lastPos = nil
			this.UserPanningTheCamera = false
		end
		if NumUnsunkTouches == 2 then
			local unsunkTouches = {}
			for touch, wasSunk in pairs(fingerTouches) do
				if not wasSunk then
					table.insert(unsunkTouches, touch)
				end
			end
			if #unsunkTouches == 2 then
				local difference = (unsunkTouches[1].Position - unsunkTouches[2].Position).magnitude
				if StartingDiff and pinchBeginZoom then
					local scale = difference / math.max(0.01, StartingDiff)
					local clampedScale = clamp(0.1, 10, scale)
					if this.ZoomEnabled then
						this:ZoomCamera(pinchBeginZoom / clampedScale)
					end
				else
					StartingDiff = difference
					pinchBeginZoom = this:GetCameraActualZoom()
				end
			end
		else
			StartingDiff = nil
			pinchBeginZoom = nil
		end
	end

	local function OnTouchEnded(input, processed)
		if fingerTouches[input] == false then
			if NumUnsunkTouches == 1 then
				panBeginLook = nil
				startPos = nil
				lastPos = nil
				this.UserPanningTheCamera = false
			elseif NumUnsunkTouches == 2 then
				StartingDiff = nil
				pinchBeginZoom = nil
			end
		end

		if fingerTouches[input] ~= nil and fingerTouches[input] == false then
			NumUnsunkTouches = NumUnsunkTouches - 1
		end
		fingerTouches[input] = nil
	end

	local function OnMouse2Down(input, processed)
		if processed then return end
		isRightMouseDown = true
		this:UpdateMouseBehavior()
		panBeginLook = this:GetCameraLook()
		startPos = input.Position
		lastPos = startPos
		this.UserPanningTheCamera = true
	end

	local function OnMouse2Up(input, processed)
		isRightMouseDown = false
		this:UpdateMouseBehavior()
		panBeginLook = nil
		startPos = nil
		lastPos = nil
		this.UserPanningTheCamera = false
	end

	local function OnMouseMoved(input, processed)
		if startPos and lastPos and panBeginLook then
			local currPos = lastPos + input.Delta
			local totalTrans = currPos - startPos
			if this.PanEnabled then
				local desiredXYVector = this:MouseTranslationToAngle(input.Delta) * (MOUSE_SENSITIVITY*sensitivity.Value)
				this.RotateInput = this.RotateInput + desiredXYVector
			end
			lastPos = currPos
		elseif this:IsInFirstPerson() or this:GetShiftLock() then
			if this.PanEnabled then
				local desiredXYVector = this:MouseTranslationToAngle(input.Delta) * (MOUSE_SENSITIVITY*sensitivity.Value)
				this.RotateInput = this.RotateInput + desiredXYVector
			end
		end
	end

	local function OnMouseWheel(input, processed)
		if not processed then
			if this.ZoomEnabled then
				this:ZoomCameraBy(clamp(-1, 1, -input.Position.Z) * 1.4)
			end
		end
	end

	local function round(num)
		return math.floor(num + 0.5)
	end

	local eight2Pi = math.pi / 4

	local function rotateVectorByAngleAndRound(camLook, rotateAngle, roundAmount)
		if camLook ~= Vector3.new(0,0,0) then
			camLook = camLook.unit
			local currAngle = math.atan2(camLook.z, camLook.x)
			local newAngle = round((math.atan2(camLook.z, camLook.x) + rotateAngle) / roundAmount) * roundAmount
			return newAngle - currAngle
		end
		return 0
	end

	local function OnKeyDown(input, processed)
		if processed then return end
		if this.ZoomEnabled then
			if input.KeyCode == Enum.KeyCode.I then
				this:ZoomCameraBy(-5)
			elseif input.KeyCode == Enum.KeyCode.O then
				this:ZoomCameraBy(5)
			end
		end
		if panBeginLook == nil and this.KeyPanEnabled then
			if input.KeyCode == Enum.KeyCode.Left then
				this.TurningLeft = true
			elseif input.KeyCode == Enum.KeyCode.Right then
				this.TurningRight = true
			elseif input.KeyCode == Enum.KeyCode.Comma then
				local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), -eight2Pi * (3/4), eight2Pi)
				if angle ~= 0 then
					this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
					this.LastUserPanCamera = tick()
					this.LastCameraTransform = nil
				end
			elseif input.KeyCode == Enum.KeyCode.Period then
				local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), eight2Pi * (3/4), eight2Pi)
				if angle ~= 0 then
					this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
					this.LastUserPanCamera = tick()
					this.LastCameraTransform = nil
				end
			elseif input.KeyCode == Enum.KeyCode.PageUp then
			--elseif input.KeyCode == Enum.KeyCode.Home then
				this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(15))
				this.LastCameraTransform = nil
			elseif input.KeyCode == Enum.KeyCode.PageDown then
			--elseif input.KeyCode == Enum.KeyCode.End then
				this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(-15))
				this.LastCameraTransform = nil
			end
		end
	end

	local function OnKeyUp(input, processed)
		if input.KeyCode == Enum.KeyCode.Left then
			this.TurningLeft = false
		elseif input.KeyCode == Enum.KeyCode.Right then
			this.TurningRight = false
		end
	end

	local lastThumbstickRotate = nil
	local numOfSeconds = 0.7
	local currentSpeed = 0
	local maxSpeed = 0.1
	local thumbstickSensitivity = 1.0
	local lastThumbstickPos = Vector2.new(0,0)
	local ySensitivity = 0.65
	local lastVelocity = nil

	-- K is a tunable parameter that changes the shape of the S-curve
	-- the larger K is the more straight/linear the curve gets
	local k = 0.35
	local lowerK = 0.8
	local function SCurveTranform(t)
		t = clamp(-1,1,t)
		if t >= 0 then
			return (k*t) / (k - t + 1)
		end
		return -((lowerK*-t) / (lowerK + t + 1))
	end

	-- DEADZONE
	local DEADZONE = 0.1
	local function toSCurveSpace(t)
		return (1 + DEADZONE) * (2*math.abs(t) - 1) - DEADZONE
	end

	local function fromSCurveSpace(t)
		return t/2 + 0.5
	end

	local function gamepadLinearToCurve(thumbstickPosition)
		local function onAxis(axisValue)
			local sign = 1
			if axisValue < 0 then
				sign = -1
			end
			local point = fromSCurveSpace(SCurveTranform(toSCurveSpace(math.abs(axisValue))))
			point = point * sign
			return clamp(-1,1,point)
		end
		return Vector2.new(onAxis(thumbstickPosition.x), onAxis(thumbstickPosition.y))
	end

	function this:UpdateGamepad()
		local gamepadPan = this.GamepadPanningCamera
		if gamepadPan then
			gamepadPan = gamepadLinearToCurve(gamepadPan)
			local currentTime = tick()
			if gamepadPan.X ~= 0 or gamepadPan.Y ~= 0 then
				this.userPanningTheCamera = true
			elseif gamepadPan == Vector2.new(0,0) then
				lastThumbstickRotate = nil
				if lastThumbstickPos == Vector2.new(0,0) then
					currentSpeed = 0
				end
			end

			local finalConstant = 0

			if lastThumbstickRotate then
				local elapsedTime = (currentTime - lastThumbstickRotate) * 10
				currentSpeed = currentSpeed + (maxSpeed * ((elapsedTime*elapsedTime)/numOfSeconds))

				if currentSpeed > maxSpeed then currentSpeed = maxSpeed end

				if lastVelocity then
					local velocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
					local velocityDeltaMag = (velocity - lastVelocity).magnitude

					if velocityDeltaMag > 12 then
						currentSpeed = currentSpeed * (20/velocityDeltaMag)
						if currentSpeed > maxSpeed then currentSpeed = maxSpeed end
					end
				end

				finalConstant = thumbstickSensitivity * currentSpeed
				lastVelocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
			end

			lastThumbstickPos = gamepadPan
			lastThumbstickRotate = currentTime

			return Vector2.new( gamepadPan.X * finalConstant, gamepadPan.Y * finalConstant * ySensitivity)
		end

		return Vector2.new(0,0)
	end

	local InputBeganConn, InputChangedConn, InputEndedConn, ShiftLockToggleConn = nil, nil, nil, nil

	function this:DisconnectInputEvents()
		if InputBeganConn then
			InputBeganConn:disconnect()
			InputBeganConn = nil
		end
		if InputChangedConn then
			InputChangedConn:disconnect()
			InputChangedConn = nil
		end
		if InputEndedConn then
			InputEndedConn:disconnect()
			InputEndedConn = nil
		end
		if ShiftLockToggleConn then
			ShiftLockToggleConn:disconnect()
			ShiftLockToggleConn = nil
		end
		this.TurningLeft = false
		this.TurningRight = false
		this.LastCameraTransform = nil
		self.LastSubjectCFrame = nil
		this.UserPanningTheCamera = false
		this.RotateInput = Vector2.new()
		this.GamepadPanningCamera = Vector2.new(0,0)

		-- Reset input states
		startPos = nil
		lastPos = nil
		panBeginLook = nil
		isRightMouseDown = false

		fingerTouches = {}
		NumUnsunkTouches = 0

		StartingDiff = nil
		pinchBeginZoom = nil

		-- Unlock mouse for example if right mouse button was being held down
		if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
			UserInputService.MouseBehavior = Enum.MouseBehavior.Default
		end
	end

	function this:ConnectInputEvents()
		InputBeganConn = UserInputService.InputBegan:connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch and IsTouch then
				OnTouchBegan(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseButton2 and not IsTouch then
				OnMouse2Down(input, processed)
			end
			-- Keyboard
			if input.UserInputType == Enum.UserInputType.Keyboard then
				OnKeyDown(input, processed)
			end
		end)

		InputChangedConn = UserInputService.InputChanged:connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch and IsTouch then
				OnTouchChanged(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseMovement and not IsTouch then
				OnMouseMoved(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseWheel and not IsTouch then
				OnMouseWheel(input, processed)
			end
		end)

		InputEndedConn = UserInputService.InputEnded:connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch and IsTouch then
				OnTouchEnded(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseButton2 and not IsTouch then
				OnMouse2Up(input, processed)
			end
			-- Keyboard
			if input.UserInputType == Enum.UserInputType.Keyboard then
				OnKeyUp(input, processed)
			end
		end)
		
		ShiftLockToggleConn = ShiftLockController.OnShiftLockToggled.Event:connect(function()
			this:UpdateMouseBehavior()
		end)

		this.RotateInput = Vector2.new()

		local getGamepadPan = function(name, state, input)
			if input.UserInputType == Enum.UserInputType.Gamepad1 and input.KeyCode == Enum.KeyCode.Thumbstick2 then

				if state == Enum.UserInputState.Cancel then
					this.GamepadPanningCamera = Vector2.new(0,0)
					return
				end

				local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
				if inputVector.magnitude > 0.1 then
					this.GamepadPanningCamera = Vector2.new(input.Position.X, -input.Position.Y)
				else
					this.GamepadPanningCamera = Vector2.new(0,0)
				end
			end
		end

		local doGamepadZoom = function(name, state, input)
			if input.UserInputType == Enum.UserInputType.Gamepad1 and input.KeyCode == Enum.KeyCode.ButtonR3 and state == Enum.UserInputState.Begin then
				if this.currentZoom > 0.5 then
					this:ZoomCamera(0)
				else
					this:ZoomCamera(10)
				end
			end
		end

		game.ContextActionService:BindAction("RootCamGamepadPan", getGamepadPan, false, Enum.KeyCode.Thumbstick2)
		game.ContextActionService:BindAction("RootCamGamepadZoom", doGamepadZoom, false, Enum.KeyCode.ButtonR3)

		-- set mouse behavior
		self:UpdateMouseBehavior()
	end

	function this:SetEnabled(newState)
		if newState ~= self.Enabled then
			self.Enabled = newState
			if self.Enabled then
				self:ConnectInputEvents()
			else
				self:DisconnectInputEvents()
			end
		end
	end

	local function OnPlayerAdded(player)
		player.Changed:connect(function(prop)
			if this.Enabled then
				if prop == "CameraMode" or prop == "CameraMaxZoomDistance" or prop == "CameraMinZoomDistance" then
					 this:ZoomCameraFixedBy(0)
				end
			end
		end)

		local function OnCharacterAdded(newCharacter)
			this:ZoomCamera(12.5)
			local humanoid = findPlayerHumanoid(player)
			local start = tick()
			while tick() - start < 0.3 and (humanoid == nil or humanoid.Torso == nil) do
				wait()
				humanoid = findPlayerHumanoid(player)
			end
			local function setLookBehindChatacter()
				if humanoid and humanoid.Torso and player.Character == newCharacter then
					local newDesiredLook = (humanoid.Torso.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
					local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, this:GetCameraLook())
					local vertShift = math.asin(this:GetCameraLook().y) - math.asin(newDesiredLook.y)
					if not IsFinite(horizontalShift) then
						horizontalShift = 0
					end
					if not IsFinite(vertShift) then
						vertShift = 0
					end
					this.RotateInput = Vector2.new(horizontalShift, vertShift)

					-- reset old camera info so follow cam doesn't rotate us
					this.LastCameraTransform = nil
				end
			end
			wait()
			setLookBehindChatacter()
		end

		player.CharacterAdded:connect(function(character)
			if this.Enabled or SetCameraOnSpawn then
				OnCharacterAdded(character)
				SetCameraOnSpawn = false
			end
		end)
		if player.Character then
			spawn(function() OnCharacterAdded(player.Character) end)
		end
	end
	if PlayersService.LocalPlayer then
		OnPlayerAdded(PlayersService.LocalPlayer)
	end
	PlayersService.ChildAdded:connect(function(child)
		if child and PlayersService.LocalPlayer == child then
			OnPlayerAdded(PlayersService.LocalPlayer)
		end
	end)

	return this
end

return CreateCamera
