local function O(O,D)return math_atan2(D.X*O.Z-D.Z*O.X,D.X*O.X+D.Z*O.Z)end local function D()local D=RootCameraCreator()local g=math_rad(220)local Y=math_rad(0)local B=math_rad(250)local f=2 local x=tick()D.LastUserPanCamera=tick()function D.Update(o)D:ProcessTweens()local S=tick()local w=S-x local J=o.UserPanningTheCamera==true local y=workspace.CurrentCamera local i=PlayersService.LocalPlayer local H=o:GetHumanoid()local m=y and y.CameraSubject local r=H and H:GetState()==HUMANOIDSTATE_CLIMBING local h=m and m:IsA("\086\101\104\105\099\108\101\083\101\097\116")local j=m and m:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")if x==nil or S-x>1 then D:ResetCameraLook()o.LastCameraTransform=nil end if x then if o:ShouldUseVRRotation()then o.RotateInput=o.RotateInput+o:GetVRRotationInput()else local O=math_min(.1,S-x)local D=0 if not(h or j)then D=D+(o.TurningLeft and-120 or 0)D=D+(o.TurningRight and 120 or 0)end local g=o:UpdateGamepad()if g~=Vector2.new(0,0)then J=true o.RotateInput=o.RotateInput+g*O end if D~=0 then J=true o.RotateInput=o.RotateInput+Vector2_new(math_rad(D*O),0)end end end if J then Y=0 D.LastUserPanCamera=tick()end local v=S-D.LastUserPanCamera<f local a=o:GetSubjectPosition()if a and(i and y)then local D=o:GetCameraZoom()if D<.5 then D=.5 end if o:GetShiftLock()and not o:IsInFirstPerson()then local O=o:RotateCamera(o:GetCameraLook(),o.RotateInput)local D=((O*XZ_VECTOR):Cross(UP_VECTOR)).unit*1.75 if IsFiniteVector3(D)then a=a+D end else if o.LastCameraTransform and not J then local D=o:IsInFirstPerson()if(r or h or j)and(x and(H and H.Torso))then if D then if o.LastSubjectCFrame and((h or j)and m:IsA("\066\097\115\101\080\097\114\116"))then local D=-O(o.LastSubjectCFrame.lookVector,m.CFrame.lookVector)if IsFinite(D)then o.RotateInput=o.RotateInput+Vector2.new(D,0)end Y=0 end elseif not v then local D=H.Torso.CFrame.lookVector if j then D=m.CFrame.lookVector end Y=clamp(0,B,Y+g*w)local f=clamp(0,1,Y*w)if not r and o:IsInFirstPerson()then f=1 end local x=O(D,o:GetCameraLook())if IsFinite(x)and math_abs(x)>.0001 then o.RotateInput=o.RotateInput+Vector2.new(x*f,0)end end elseif not(D or v)and not VRService.VREnabled then local D=-(o.LastCameraTransform.p-a)local g=O(D,o:GetCameraLook())local Y=.4 if IsFinite(g)and(math.abs(g)>.0001 and math_abs(g)>Y*w)then o.RotateInput=o.RotateInput+Vector2.new(g,0)end end end end local f=o:RotateCamera(o:GetCameraLook(),o.RotateInput)o.RotateInput=ZERO_VECTOR2 if VRService.VREnabled then y.Focus=o:GetVRFocus(a,w)elseif o:IsPortraitMode()then y.Focus=CFrame_new(a+PORTRAIT_OFFSET)else y.Focus=CFrame_new(a)end y.CFrame=CFrame_new(y.Focus.p-D*f,y.Focus.p)+Vector3.new(0,o:GetCameraHeight(),0)o.LastCameraTransform=y.CFrame o.LastCameraFocus=y.Focus if h or j and m:IsA("\066\097\115\101\080\097\114\116")then o.LastSubjectCFrame=m.CFrame else o.LastSubjectCFrame=nil end end x=S end return D end return D

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
