local function O(O,X)return math.acos(O:Dot(X)/(O.magnitude*X.magnitude))end local X={}local function w(O)local w=O and O.Character if w then local G=X[O]if G and G.Parent==w then return G else X[O]=nil for w,G in pairs(w:GetChildren())do if G:IsA("\072\117\109\097\110\111\105\100")then X[O]=G return G end end end end end local function G()local O=RootCameraCreator()local X=math.rad(250)local G=math.rad(0)local m=math.rad(250)local v=tick()function O.Update(k)local C=tick()local r=k.UserPanningTheCamera==true if v==nil or C-v>1 then O:ResetCameraLook()k.LastCameraTransform=nil end if v then local O=math.min(.5,C-v)local X=k.TurningLeft and-120 or 0 X=X+(k.TurningRight and 120 or 0)if X~=0 then r=true end k:RotateCamera(k:GetCameraLook(),Vector2.new(math.rad(X*O),0))end if r then G=0 end local c=workspace.CurrentCamera local M=PlayersService.LocalPlayer local a=k:GetSubjectPosition()if a and(M and c)then local O=k:GetCameraZoom()if O<=0 then O=.1 end if k:GetShiftLock()and not k:IsInFirstPerson()then local X=((k:GetCameraLook()*XZ_VECTOR):Cross(UP_VECTOR)).unit*1.75 if IsFiniteVector3(X)then a=a+X end O=math.max(O,5)else if k.LastCameraTransform and not r then local O=w(M)local r=c and c.CameraSubject local N=O and O:GetState()==Enum.HumanoidStateType.Climbing local z=r and r:IsA("\086\101\104\105\099\108\101\083\101\097\116")local A=r and r:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")if(N or z or A)and(v and(O and O.Torso))then local w=O.Torso.CFrame.lookVector if A then w=r.CFrame.lookVector end local c=C-v G=clamp(0,m,G+X*c)local M=clamp(0,1,G*c)if not N and k:IsInFirstPerson()then M=1 end local a=findAngleBetweenXZVectors(w,k:GetCameraLook())if IsFinite(a)then k:RotateCamera(k:GetCameraLook(),Vector3.new(a*M,0,0))end elseif not k:IsInFirstPerson()then local O=-(k.LastCameraTransform.p-a)local X=findAngleBetweenXZVectors(O,k:GetCameraLook())if IsFinite(X)then k:RotateCamera(k:GetCameraLook(),Vector3.new(X,0,0))end end end end c.Focus=CFrame.new(a)c.CoordinateFrame=CFrame.new(c.Focus.p-O*k:GetCameraLook(),c.Focus.p)k.LastCameraTransform=c.CoordinateFrame end v=C end return O end return G
-- May return NaN or inf or -inf
local function absoluteAngleBetween3dVectors(vec1, vec2)
	return math.acos(vec1:Dot(vec2) / (vec1.magnitude * vec2.magnitude))
end

local humanoidCache = {}
local function findPlayerHumanoid(player)
	local character = player and player.Character
	if character then
		local resultHumanoid = humanoidCache[player]
		if resultHumanoid and resultHumanoid.Parent == character then
			return resultHumanoid
		else
			humanoidCache[player] = nil -- Bust Old Cache
			for _, child in pairs(character:GetChildren()) do
				if child:IsA("Humanoid") then
					humanoidCache[player] = child
					return child
				end
			end
		end
	end
end

local function CreateFollowCamera()
	local module = RootCameraCreator()

	local tweenAcceleration = math.rad(250)
	local tweenSpeed = math.rad(0)
	local tweenMaxSpeed = math.rad(250)

	local lastUpdate = tick()
	function module:Update()
		local now = tick()
		local userPanningTheCamera = (self.UserPanningTheCamera == true)

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

		if lastUpdate then
			-- Cap out the delta to 0.5 so we don't get some crazy things when we re-resume from
			local delta = math.min(0.5, now - lastUpdate)
			local angle = self.TurningLeft and -120 or 0
			angle = angle + (self.TurningRight and 120 or 0)
			if angle ~= 0 then
				userPanningTheCamera = true
			end
			self:RotateCamera(self:GetCameraLook(), Vector2.new(math.rad(angle * delta), 0))
		end

		-- Reset tween speed if user is panning
		if userPanningTheCamera then
			tweenSpeed = 0
		end

		local camera = workspace.CurrentCamera
		local player = PlayersService.LocalPlayer
		local subjectPosition = self:GetSubjectPosition()

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

			if self:GetShiftLock() and not self:IsInFirstPerson() then
				local offset = ((self:GetCameraLook() * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
				-- Check for NaNs
				if IsFiniteVector3(offset) then
					subjectPosition = subjectPosition + offset
				end
				zoom = math.max(zoom, 5)
			else
				if self.LastCameraTransform and not userPanningTheCamera then
					local humanoid = findPlayerHumanoid(player)
					local cameraSubject = camera and camera.CameraSubject
					local isClimbing = humanoid and humanoid:GetState() == Enum.HumanoidStateType.Climbing
					local isInVehicle = cameraSubject and cameraSubject:IsA("VehicleSeat")
					local isOnASkateboard = cameraSubject and cameraSubject:IsA("SkateboardPlatform")
					if
						(isClimbing or isInVehicle or isOnASkateboard)
						and lastUpdate
						and humanoid
						and humanoid.Torso
					then
						local forwardVector = humanoid.Torso.CFrame.lookVector
						if isOnASkateboard then
							forwardVector = cameraSubject.CFrame.lookVector
						end
						local timeDelta = (now - lastUpdate)

						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) then
							self:RotateCamera(self:GetCameraLook(), Vector3.new(y * percent, 0, 0))
						end
					elseif not self:IsInFirstPerson() then
						local lastVec = -(self.LastCameraTransform.p - subjectPosition)
						local y = findAngleBetweenXZVectors(lastVec, self:GetCameraLook())
						-- Check for NaNs
						if IsFinite(y) then
							self:RotateCamera(self:GetCameraLook(), Vector3.new(y, 0, 0))
						end
					end
				end
			end

			camera.Focus = CFrame.new(subjectPosition)
			camera.CoordinateFrame = CFrame.new(camera.Focus.p - (zoom * self:GetCameraLook()), camera.Focus.p)
			self.LastCameraTransform = camera.CoordinateFrame
		end

		lastUpdate = now
	end

	return module
end

return CreateFollowCamera
