local function I(I,q)return math.atan2(q.X*I.Z-q.Z*I.X,q.X*I.X+q.Z*I.Z)end local function q(I,q)local H=(q:Cross(I)).unit local B=(I:Cross(H)).unit I=I.unit return CFrame.new(0,0,0,H.x,B.x,I.x,H.y,B.y,I.y,H.z,B.z,I.z)end local function H()local H=RootCameraCreator()local B=math.rad(220)local A=math.rad(0)local i=math.rad(250)local w=2 local O=nil local e=.7 local g=0 local p=.1 local R=1 local V=Vector2.new(0,0)local n=.8 local x=nil local d=tick()H.LastUserPanCamera=tick()function H.Update(O)local e=tick()local g=O.UserPanningTheCamera==true local p=workspace.CurrentCamera local R=PlayersService.LocalPlayer local V=O:GetHumanoid()local n=p and p.CameraSubject local x=n and n:IsA("\086\101\104\105\099\108\101\083\101\097\116")local E=n and n:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")if d==nil or e-d>1 then H:ResetCameraLook()O.LastCameraTransform=nil end if d then local I=math.min(.5,e-d)local q=0 if not(x or E)then q=q+(O.TurningLeft and-120 or 0)q=q+(O.TurningRight and 120 or 0)end local H=O:UpdateGamepad()if H~=Vector2.new(0,0)then g=true O.RotateInput=O.RotateInput+H end if q~=0 then g=true O.RotateInput=O.RotateInput+Vector2.new(math.rad(q*I),0)end end if g then A=0 H.LastUserPanCamera=tick()end local T=e-H.LastUserPanCamera<w local K=O:GetSubjectPosition()if K and(R and p)then local H=O:GetCameraZoom()if H<.5 then H=.5 end if O:GetShiftLock()and not O:IsInFirstPerson()then local I=O:RotateCamera(O:GetCameraLook(),O.RotateInput)local q=((I*XZ_VECTOR):Cross(UP_VECTOR)).unit*1.75 if IsFiniteVector3(q)then K=K+q end else if O.LastCameraTransform and not g then local q=O:IsInFirstPerson()if(x or E)and(d and(V and V.Torso))then if q then if O.LastSubjectCFrame and((x or E)and n:IsA("\066\097\115\101\080\097\114\116"))then local q=-I(O.LastSubjectCFrame.lookVector,n.CFrame.lookVector)if IsFinite(q)then O.RotateInput=O.RotateInput+Vector2.new(q,0)end A=0 end elseif not T then local q=V.Torso.CFrame.lookVector if E then q=n.CFrame.lookVector end local H=e-d A=clamp(0,i,A+B*H)local w=clamp(0,1,A*H)if O:IsInFirstPerson()then w=1 end local g=I(q,O:GetCameraLook())if IsFinite(g)and math.abs(g)>.0001 then O.RotateInput=O.RotateInput+Vector2.new(g*w,0)end end end end end local w=O:RotateCamera(O:GetCameraLook(),O.RotateInput)O.RotateInput=Vector2.new()p.Focus=CFrame.new(K)local R=(O.DownVector or{Value=Vector3.new(0,-1,0)}).Value local b=q(-w,-R)p.CoordinateFrame=(CFrame.new(p.Focus.p)*b)*CFrame.new(0,0,H)O.LastCameraTransform=p.CoordinateFrame if x or E and n:IsA("\066\097\115\101\080\097\114\116")then O.LastSubjectCFrame=n.CFrame else O.LastSubjectCFrame=nil end end d=e end return H end return H
-- May return NaN or inf or -inf
-- This is a way of finding the angle between the two vectors:
local function findAngleBetweenXZVectors(vec2, vec1)
	return math.atan2(vec1.X * vec2.Z - vec1.Z * vec2.X, vec1.X * vec2.X + vec1.Z * vec2.Z)
end

local function GetOrientationZY(backVector, upVector)
	local rightVector = upVector:Cross(backVector).unit
	local upVector = backVector:Cross(rightVector).unit
	backVector = backVector.unit
	return CFrame.new(
		0,
		0,
		0,
		rightVector.x,
		upVector.x,
		backVector.x,
		rightVector.y,
		upVector.y,
		backVector.y,
		rightVector.z,
		upVector.z,
		backVector.z
	)
end

local function CreateClassicCamera()
	local module = RootCameraCreator()

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

	local lastThumbstickRotate = nil
	local numOfSeconds = 0.7
	local currentSpeed = 0
	local maxSpeed = 0.1
	local thumbstickSensitivity = 1
	local lastThumbstickPos = Vector2.new(0, 0)
	local ySensitivity = 0.8
	local lastVelocity = nil

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

		local userPanningTheCamera = (self.UserPanningTheCamera == true)
		local camera = workspace.CurrentCamera
		local player = PlayersService.LocalPlayer
		local humanoid = self:GetHumanoid()
		local cameraSubject = camera and camera.CameraSubject
		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
			-- 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 = 0
			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
			end

			if angle ~= 0 then
				userPanningTheCamera = true
				self.RotateInput = self.RotateInput + Vector2.new(math.rad(angle * delta), 0)
			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
				-- We need to use the right vector of the camera after rotation, not before
				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 (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
							local timeDelta = (now - lastUpdate)

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

							local percent = clamp(0, 1, tweenSpeed * timeDelta)
							if self:IsInFirstPerson() then
								percent = 1
							end

							local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
							if IsFinite(y) and math.abs(y) > 0.0001 then
								self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
							end
						end
					end
				end
			end

			local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
			self.RotateInput = Vector2.new()

			camera.Focus = CFrame.new(subjectPosition)
			local downVector = (self.DownVector or { Value = Vector3.new(0, -1, 0) }).Value
			local camOrientation = GetOrientationZY(-newLookVector, -downVector)
			camera.CoordinateFrame = CFrame.new(camera.Focus.p) * camOrientation * CFrame.new(0, 0, zoom)
			self.LastCameraTransform = camera.CoordinateFrame
			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 CreateClassicCamera
