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

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
				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
				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
