local function I(I,b)return math_atan2(b.X*I.Z-b.Z*I.X,b.X*I.X+b.Z*I.Z)end local function b()local b=RootCameraCreator()local V=math_rad(220)local p=math_rad(0)local h=math_rad(250)local t=2 local i=tick()b.LastUserPanCamera=tick()function b.Update(Z)b:ProcessTweens()local F=tick()local T=F-i local C=Z.UserPanningTheCamera==true local o=workspace.CurrentCamera local M=PlayersService.LocalPlayer local r=Z:GetHumanoid()local G=o and o.CameraSubject local X=r and r:GetState()==HUMANOIDSTATE_CLIMBING local B=G and G:IsA("\086\101\104\105\099\108\101\083\101\097\116")local K=G and G:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")if i==nil or F-i>1 then b:ResetCameraLook()Z.LastCameraTransform=nil end if i then if Z:ShouldUseVRRotation()then Z.RotateInput=Z.RotateInput+Z:GetVRRotationInput()else local I=math_min(.1,F-i)local b=0 if not(B or K)then b=b+(Z.TurningLeft and-120 or 0)b=b+(Z.TurningRight and 120 or 0)end local V=Z:UpdateGamepad()if V~=Vector2.new(0,0)then C=true Z.RotateInput=Z.RotateInput+V*I end if b~=0 then C=true Z.RotateInput=Z.RotateInput+Vector2_new(math_rad(b*I),0)end end end if C then p=0 b.LastUserPanCamera=tick()end local l=F-b.LastUserPanCamera<t local c=Z:GetSubjectPosition()if c and(M and o)then local b=Z:GetCameraZoom()if b<.5 then b=.5 end if Z:GetShiftLock()and not Z:IsInFirstPerson()then local I=Z:RotateCamera(Z:GetCameraLook(),Z.RotateInput)local b=((I*XZ_VECTOR):Cross(UP_VECTOR)).unit*1.75 if IsFiniteVector3(b)then c=c+b end else if Z.LastCameraTransform and not C then local b=Z:IsInFirstPerson()if(X or B or K)and(i and(r and r.Torso))then if b then if Z.LastSubjectCFrame and((B or K)and G:IsA("\066\097\115\101\080\097\114\116"))then local b=-I(Z.LastSubjectCFrame.lookVector,G.CFrame.lookVector)if IsFinite(b)then Z.RotateInput=Z.RotateInput+Vector2.new(b,0)end p=0 end elseif not l then local b=r.Torso.CFrame.lookVector if K then b=G.CFrame.lookVector end p=clamp(0,h,p+V*T)local t=clamp(0,1,p*T)if not X and Z:IsInFirstPerson()then t=1 end local i=I(b,Z:GetCameraLook())if IsFinite(i)and math_abs(i)>.0001 then Z.RotateInput=Z.RotateInput+Vector2.new(i*t,0)end end elseif not(b or l)and not VRService.VREnabled then local b=-(Z.LastCameraTransform.p-c)local V=I(b,Z:GetCameraLook())local p=.4 if IsFinite(V)and(math.abs(V)>.0001 and math_abs(V)>p*T)then Z.RotateInput=Z.RotateInput+Vector2.new(V,0)end end end end local t=Z:RotateCamera(Z:GetCameraLook(),Z.RotateInput)Z.RotateInput=ZERO_VECTOR2 if VRService.VREnabled then o.Focus=Z:GetVRFocus(c,T)elseif Z:IsPortraitMode()then o.Focus=CFrame_new(c+PORTRAIT_OFFSET)else o.Focus=CFrame_new(c)end o.CFrame=CFrame_new(o.Focus.p-b*t,o.Focus.p)+Vector3.new(0,Z:GetCameraHeight(),0)Z.LastCameraTransform=o.CFrame Z.LastCameraFocus=o.Focus if B or K and G:IsA("\066\097\115\101\080\097\114\116")then Z.LastSubjectCFrame=G.CFrame else Z.LastSubjectCFrame=nil end end i=F end return b end return b
-- May return NaN or inf or -inf
local function findAngleBetweenXZVectors(vec2, vec1)
	-- This is a way of finding the angle between the two vectors:
	return math_atan2(vec1.X * vec2.Z - vec1.Z * vec2.X, vec1.X * vec2.X + vec1.Z * vec2.Z)
end

local function CreateFollowCamera()
	local module = RootCameraCreator()

	local tweenAcceleration = math_rad(220)
	local tweenSpeed = math_rad(0)
	local tweenMaxSpeed = math_rad(250)
	local timeBeforeAutoRotate = 2

	local lastUpdate = tick()
	module.LastUserPanCamera = tick()
	function module:Update()
		module:ProcessTweens()
		local now = tick()
		local timeDelta = (now - lastUpdate)

		local userPanningTheCamera = (self.UserPanningTheCamera == true)
		local camera = workspace.CurrentCamera
		local player = PlayersService.LocalPlayer
		local humanoid = self:GetHumanoid()
		local cameraSubject = camera and camera.CameraSubject
		local isClimbing = humanoid and humanoid:GetState() == HUMANOIDSTATE_CLIMBING
		local isInVehicle = cameraSubject and cameraSubject:IsA("VehicleSeat")
		local isOnASkateboard = cameraSubject and cameraSubject:IsA("SkateboardPlatform")

		if lastUpdate == nil or now - lastUpdate > 1 then
			module:ResetCameraLook()
			self.LastCameraTransform = nil
		end

		if lastUpdate then
			if self:ShouldUseVRRotation() then
				self.RotateInput = self.RotateInput + self:GetVRRotationInput()
			else
				-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
				local delta = math_min(0.1, now - lastUpdate)
				local angle = 0
				-- NOTE: Traditional follow camera does not rotate with arrow keys
				if not (isInVehicle or isOnASkateboard) then
					angle = angle + (self.TurningLeft and -120 or 0)
					angle = angle + (self.TurningRight and 120 or 0)
				end

				local gamepadRotation = self:UpdateGamepad()
				if gamepadRotation ~= Vector2.new(0, 0) then
					userPanningTheCamera = true
					self.RotateInput = self.RotateInput + (gamepadRotation * delta)
				end

				if angle ~= 0 then
					userPanningTheCamera = true
					self.RotateInput = self.RotateInput + Vector2_new(math_rad(angle * delta), 0)
				end
			end
		end

		-- Reset tween speed if user is panning
		if userPanningTheCamera then
			tweenSpeed = 0
			module.LastUserPanCamera = tick()
		end

		local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate

		local subjectPosition = self:GetSubjectPosition()
		if subjectPosition and player and camera then
			local zoom = self:GetCameraZoom()
			if zoom < 0.5 then
				zoom = 0.5
			end

			if self:GetShiftLock() and not self:IsInFirstPerson() then
				local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
				local offset = ((newLookVector * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
				if IsFiniteVector3(offset) then
					subjectPosition = subjectPosition + offset
				end
			else
				if self.LastCameraTransform and not userPanningTheCamera then
					local isInFirstPerson = self:IsInFirstPerson()
					if
						(isClimbing or isInVehicle or isOnASkateboard)
						and lastUpdate
						and humanoid
						and humanoid.Torso
					then
						if isInFirstPerson then
							if
								self.LastSubjectCFrame
								and (isInVehicle or isOnASkateboard)
								and cameraSubject:IsA("BasePart")
							then
								local y = -findAngleBetweenXZVectors(
									self.LastSubjectCFrame.lookVector,
									cameraSubject.CFrame.lookVector
								)
								if IsFinite(y) then
									self.RotateInput = self.RotateInput + Vector2.new(y, 0)
								end
								tweenSpeed = 0
							end
						elseif not userRecentlyPannedCamera then
							local forwardVector = humanoid.Torso.CFrame.lookVector
							if isOnASkateboard then
								forwardVector = cameraSubject.CFrame.lookVector
							end

							tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)

							local percent = clamp(0, 1, tweenSpeed * timeDelta)
							if not isClimbing and self:IsInFirstPerson() then
								percent = 1
							end
							local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
							-- Check for NaN
							if IsFinite(y) and math_abs(y) > 0.0001 then
								self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
							end
						end
					elseif not (isInFirstPerson or userRecentlyPannedCamera) and not VRService.VREnabled then
						local lastVec = -(self.LastCameraTransform.p - subjectPosition)

						local y = findAngleBetweenXZVectors(lastVec, self:GetCameraLook())

						-- This cutoff is to decide if the humanoid's angle of movement,
						-- relative to the camera's look vector, is enough that
						-- we want the camera to be following them. The point is to provide
						-- a sizable deadzone to allow more precise forward movements.
						local thetaCutoff = 0.4

						-- Check for NaNs
						if IsFinite(y) and math.abs(y) > 0.0001 and math_abs(y) > thetaCutoff * timeDelta then
							self.RotateInput = self.RotateInput + Vector2.new(y, 0)
						end
					end
				end
			end
			local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
			self.RotateInput = ZERO_VECTOR2

			if VRService.VREnabled then
				camera.Focus = self:GetVRFocus(subjectPosition, timeDelta)
			elseif self:IsPortraitMode() then
				camera.Focus = CFrame_new(subjectPosition + PORTRAIT_OFFSET)
			else
				camera.Focus = CFrame_new(subjectPosition)
			end
			camera.CFrame = CFrame_new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p)
				+ Vector3.new(0, self:GetCameraHeight(), 0)

			self.LastCameraTransform = camera.CFrame
			self.LastCameraFocus = camera.Focus
			if isInVehicle or isOnASkateboard and cameraSubject:IsA("BasePart") then
				self.LastSubjectCFrame = cameraSubject.CFrame
			else
				self.LastSubjectCFrame = nil
			end
		end

		lastUpdate = now
	end

	return module
end

return CreateFollowCamera
