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