function BaseCamera.AdjustTouchSensitivity(B,a,f)local l=game.Workspace.CurrentCamera and game.Workspace.CurrentCamera.CFrame if not l then return f end local e=l:ToEulerAnglesYXZ()local Q=TOUCH_SENSITIVTY_ADJUST_MAX_Y if e>TOUCH_ADJUST_AREA_UP and a.Y<0 then local a=(e-TOUCH_ADJUST_AREA_UP)/(MAX_Y-TOUCH_ADJUST_AREA_UP)a=1-(1-a)^3 Q=TOUCH_SENSITIVTY_ADJUST_MAX_Y-a*(TOUCH_SENSITIVTY_ADJUST_MAX_Y-TOUCH_SENSITIVTY_ADJUST_MIN_Y)elseif e<TOUCH_ADJUST_AREA_DOWN and a.Y>0 then local a=(e-TOUCH_ADJUST_AREA_DOWN)/(MIN_Y-TOUCH_ADJUST_AREA_DOWN)a=1-(1-a)^3 Q=TOUCH_SENSITIVTY_ADJUST_MAX_Y-a*(TOUCH_SENSITIVTY_ADJUST_MAX_Y-TOUCH_SENSITIVTY_ADJUST_MIN_Y)end return Vector2.new(f.X,f.Y*Q)end function BaseCamera.OnTouchBegan(B,a,f)local l=B.isDynamicThumbstickEnabled and not f if l then if B.dynamicTouchInput==nil and isInDynamicThumbstickArea(a)then B.dynamicTouchInput=a return end B.fingerTouches[a]=f B.inputStartPositions[a]=a.Position B.inputStartTimes[a]=tick()B.numUnsunkTouches=B.numUnsunkTouches+1 end end function BaseCamera.OnTouchChanged(B,a,f)if B.fingerTouches[a]==nil then if B.isDynamicThumbstickEnabled then return end B.fingerTouches[a]=f if not f then B.numUnsunkTouches=B.numUnsunkTouches+1 end end if B.numUnsunkTouches==1 then if B.fingerTouches[a]==false then B.panBeginLook=B.panBeginLook or B:GetCameraLookVector()B.startPos=B.startPos or a.Position B.lastPos=B.lastPos or B.startPos B.userPanningTheCamera=true local f=a.Position-B.lastPos f=Vector2.new(f.X,f.Y*UserGameSettings:GetCameraYInvertValue())if B.panEnabled then local a=TOUCH_SENSITIVTY if FFlagUserTouchSensitivityAdjust then B:AdjustTouchSensitivity(f,TOUCH_SENSITIVTY)end local l=B:InputTranslationToCameraAngleChange(f,a)B.rotateInput=B.rotateInput+l end B.lastPos=a.Position end else B.panBeginLook=nil B.startPos=nil B.lastPos=nil B.userPanningTheCamera=false end if B.numUnsunkTouches==2 then local a={}for f,B in pairs(B.fingerTouches)do if not B then table.insert(a,f)end end if#a==2 then local f=(a[1].Position-a[2].Position).magnitude if B.startingDiff and B.pinchBeginZoom then local a=f/math.max(.01,B.startingDiff)local l=Util.Clamp(.1,10,a)if B.distanceChangeEnabled then B:SetCameraToSubjectDistance(B.pinchBeginZoom/l)end else B.startingDiff=f B.pinchBeginZoom=B:GetCameraToSubjectDistance()end end else B.startingDiff=nil B.pinchBeginZoom=nil end end function BaseCamera.OnTouchEnded(B,a,f)if a==B.dynamicTouchInput then B.dynamicTouchInput=nil return end if B.fingerTouches[a]==false then if B.numUnsunkTouches==1 then B.panBeginLook=nil B.startPos=nil B.lastPos=nil B.userPanningTheCamera=false elseif B.numUnsunkTouches==2 then B.startingDiff=nil B.pinchBeginZoom=nil end end if B.fingerTouches[a]~=nil and B.fingerTouches[a]==false then B.numUnsunkTouches=B.numUnsunkTouches-1 end B.fingerTouches[a]=nil B.inputStartPositions[a]=nil B.inputStartTimes[a]=nil end function BaseCamera.OnMouse2Down(B,a,f)if f then return end B.isRightMouseDown=true B:OnMousePanButtonPressed(a,f)end function BaseCamera.OnMouse2Up(B,a,f)B.isRightMouseDown=false B:OnMousePanButtonReleased(a,f)end function BaseCamera.OnMouse3Down(B,a,f)if f then return end B.isMiddleMouseDown=true B:OnMousePanButtonPressed(a,f)end function BaseCamera.OnMouse3Up(B,a,f)B.isMiddleMouseDown=false B:OnMousePanButtonReleased(a,f)end function BaseCamera.OnMouseMoved(B,a,f)if not B.hasGameLoaded and VRService.VREnabled then return end local l=a.Delta l=Vector2.new(l.X,l.Y*UserGameSettings:GetCameraYInvertValue())if B.panEnabled and(B.startPos and(B.lastPos and B.panBeginLook)or B.inFirstPerson or B.inMouseLockedMode)then local a=B:InputTranslationToCameraAngleChange(l,MOUSE_SENSITIVITY)B.rotateInput=B.rotateInput+a end if B.startPos and(B.lastPos and B.panBeginLook)then B.lastPos=B.lastPos+a.Delta end end function BaseCamera.OnMousePanButtonPressed(B,a,f)if f then return end B:UpdateMouseBehavior()B.panBeginLook=B.panBeginLook or B:GetCameraLookVector()B.startPos=B.startPos or a.Position B.lastPos=B.lastPos or B.startPos B.userPanningTheCamera=true end function BaseCamera.OnMousePanButtonReleased(B,a,f)B:UpdateMouseBehavior()if not(B.isRightMouseDown or B.isMiddleMouseDown)then B.panBeginLook=nil B.startPos=nil B.lastPos=nil B.userPanningTheCamera=false end end function BaseCamera.OnMouseWheel(B,a,f)if not B.hasGameLoaded and VRService.VREnabled then return end if not f then if B.distanceChangeEnabled then local f=Util.Clamp(-1,1,-a.Position.Z)local l if B.inFirstPerson and f>0 then l=FIRST_PERSON_DISTANCE_THRESHOLD else l=(B.currentSubjectDistance+(.156*B.currentSubjectDistance)*f)+1.7*math.sign(f)end B:SetCameraToSubjectDistance(l)end end end function BaseCamera.UpdateMouseBehavior(a)if a.inFirstPerson or a.inMouseLockedMode then UserGameSettings.RotationType=Enum.RotationType.CameraRelative UserInputService.MouseBehavior=Enum.MouseBehavior.LockCenter else UserGameSettings.RotationType=Enum.RotationType.MovementRelative if a.isRightMouseDown or a.isMiddleMouseDown then UserInputService.MouseBehavior=Enum.MouseBehavior.LockCurrentPosition else UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end end function BaseCamera.UpdateForDistancePropertyChange(a)a:SetCameraToSubjectDistance(a.currentSubjectDistance)end function BaseCamera.SetCameraToSubjectDistance(f,a)local B=Players.LocalPlayer local l=f.currentSubjectDistance if B.CameraMode==Enum.CameraMode.LockFirstPerson then f.currentSubjectDistance=.5 if not f.inFirstPerson then f:EnterFirstPerson()end else local l=Util.Clamp(B.CameraMinZoomDistance,B.CameraMaxZoomDistance,a)if l<FIRST_PERSON_DISTANCE_THRESHOLD then f.currentSubjectDistance=.5 if not f.inFirstPerson then f:EnterFirstPerson()end else f.currentSubjectDistance=l if f.inFirstPerson then f:LeaveFirstPerson()end end end ZoomController.SetZoomParameters(f.currentSubjectDistance,math.sign(a-l))return f.currentSubjectDistance end function BaseCamera.SetCameraType(f,a)f.cameraType=a end function BaseCamera.GetCameraType(a)return a.cameraType end

function BaseCamera:AdjustTouchSensitivity(delta, sensitivity)
	local cameraCFrame = game.Workspace.CurrentCamera and game.Workspace.CurrentCamera.CFrame
	if not cameraCFrame then
		return sensitivity
	end
	local currPitchAngle = cameraCFrame:ToEulerAnglesYXZ()

	local multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y
	if currPitchAngle > TOUCH_ADJUST_AREA_UP and delta.Y < 0 then
		local fractionAdjust = (currPitchAngle - TOUCH_ADJUST_AREA_UP)/(MAX_Y - TOUCH_ADJUST_AREA_UP)
		fractionAdjust = 1 - (1 - fractionAdjust)^3
		multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y - fractionAdjust * (
			TOUCH_SENSITIVTY_ADJUST_MAX_Y - TOUCH_SENSITIVTY_ADJUST_MIN_Y)
	elseif currPitchAngle < TOUCH_ADJUST_AREA_DOWN and delta.Y > 0 then
		local fractionAdjust = (currPitchAngle - TOUCH_ADJUST_AREA_DOWN)/(MIN_Y - TOUCH_ADJUST_AREA_DOWN)
		fractionAdjust = 1 - (1 - fractionAdjust)^3
		multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y - fractionAdjust * (
			TOUCH_SENSITIVTY_ADJUST_MAX_Y - TOUCH_SENSITIVTY_ADJUST_MIN_Y)
	end

	return Vector2.new(
		sensitivity.X,
		sensitivity.Y * multiplierY
	)
end

function BaseCamera:OnTouchBegan(input, processed)
	local canUseDynamicTouch = self.isDynamicThumbstickEnabled and not processed
	if canUseDynamicTouch then
		if self.dynamicTouchInput == nil and isInDynamicThumbstickArea(input) then
			-- First input in the dynamic thumbstick area should always be ignored for camera purposes
			-- Even if the dynamic thumbstick does not process it immediately
			self.dynamicTouchInput = input
			return
		end
		self.fingerTouches[input] = processed
		self.inputStartPositions[input] = input.Position
		self.inputStartTimes[input] = tick()
		self.numUnsunkTouches = self.numUnsunkTouches + 1
	end
end

function BaseCamera:OnTouchChanged(input, processed)
	if self.fingerTouches[input] == nil then
		if self.isDynamicThumbstickEnabled then
			return
		end
		self.fingerTouches[input] = processed
		if not processed then
			self.numUnsunkTouches = self.numUnsunkTouches + 1
		end
	end

	if self.numUnsunkTouches == 1 then
		if self.fingerTouches[input] == false then
			self.panBeginLook = self.panBeginLook or self:GetCameraLookVector()
			self.startPos = self.startPos or input.Position
			self.lastPos = self.lastPos or self.startPos
			self.userPanningTheCamera = true

			local delta = input.Position - self.lastPos
			delta = Vector2.new(delta.X, delta.Y * UserGameSettings:GetCameraYInvertValue())
			if self.panEnabled then
				local adjustedTouchSensitivity = TOUCH_SENSITIVTY
				if FFlagUserTouchSensitivityAdjust then
					self:AdjustTouchSensitivity(delta, TOUCH_SENSITIVTY)
				end

				local desiredXYVector = self:InputTranslationToCameraAngleChange(delta, adjustedTouchSensitivity)
				self.rotateInput = self.rotateInput + desiredXYVector
			end
			self.lastPos = input.Position
		end
	else
		self.panBeginLook = nil
		self.startPos = nil
		self.lastPos = nil
		self.userPanningTheCamera = false
	end
	if self.numUnsunkTouches == 2 then
		local unsunkTouches = {}
		for touch, wasSunk in pairs(self.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 self.startingDiff and self.pinchBeginZoom then
				local scale = difference / math.max(0.01, self.startingDiff)
				local clampedScale = Util.Clamp(0.1, 10, scale)
				if self.distanceChangeEnabled then
					self:SetCameraToSubjectDistance(self.pinchBeginZoom / clampedScale)
				end
			else
				self.startingDiff = difference
				self.pinchBeginZoom = self:GetCameraToSubjectDistance()
			end
		end
	else
		self.startingDiff = nil
		self.pinchBeginZoom = nil
	end
end

function BaseCamera:OnTouchEnded(input, processed)
	if input == self.dynamicTouchInput then
		self.dynamicTouchInput = nil
		return
	end

	if self.fingerTouches[input] == false then
		if self.numUnsunkTouches == 1 then
			self.panBeginLook = nil
			self.startPos = nil
			self.lastPos = nil
			self.userPanningTheCamera = false
		elseif self.numUnsunkTouches == 2 then
			self.startingDiff = nil
			self.pinchBeginZoom = nil
		end
	end

	if self.fingerTouches[input] ~= nil and self.fingerTouches[input] == false then
		self.numUnsunkTouches = self.numUnsunkTouches - 1
	end
	self.fingerTouches[input] = nil
	self.inputStartPositions[input] = nil
	self.inputStartTimes[input] = nil
end

function BaseCamera:OnMouse2Down(input, processed)
	if processed then return end

	self.isRightMouseDown = true
	self:OnMousePanButtonPressed(input, processed)
end

function BaseCamera:OnMouse2Up(input, processed)
	self.isRightMouseDown = false
	self:OnMousePanButtonReleased(input, processed)
end

function BaseCamera:OnMouse3Down(input, processed)
	if processed then return end

	self.isMiddleMouseDown = true
	self:OnMousePanButtonPressed(input, processed)
end

function BaseCamera:OnMouse3Up(input, processed)
	self.isMiddleMouseDown = false
	self:OnMousePanButtonReleased(input, processed)
end

function BaseCamera:OnMouseMoved(input, processed)
	if not self.hasGameLoaded and VRService.VREnabled then
		return
	end

	local inputDelta = input.Delta
	inputDelta = Vector2.new(inputDelta.X, inputDelta.Y * UserGameSettings:GetCameraYInvertValue())

	if self.panEnabled and ((self.startPos and self.lastPos and self.panBeginLook) or self.inFirstPerson or self.inMouseLockedMode) then
		local desiredXYVector = self:InputTranslationToCameraAngleChange(inputDelta,MOUSE_SENSITIVITY)
		self.rotateInput = self.rotateInput + desiredXYVector
	end

	if self.startPos and self.lastPos and self.panBeginLook then
		self.lastPos = self.lastPos + input.Delta
	end
end

function BaseCamera:OnMousePanButtonPressed(input, processed)
	if processed then return end
	self:UpdateMouseBehavior()
	self.panBeginLook = self.panBeginLook or self:GetCameraLookVector()
	self.startPos = self.startPos or input.Position
	self.lastPos = self.lastPos or self.startPos
	self.userPanningTheCamera = true
end

function BaseCamera:OnMousePanButtonReleased(input, processed)
	self:UpdateMouseBehavior()
	if not (self.isRightMouseDown or self.isMiddleMouseDown) then
		self.panBeginLook = nil
		self.startPos = nil
		self.lastPos = nil
		self.userPanningTheCamera = false
	end
end

function BaseCamera:OnMouseWheel(input, processed)  -- remove with FFlagUserPointerActionsInPlayerScripts
	if not self.hasGameLoaded and VRService.VREnabled then
		return
	end
	if not processed then
		if self.distanceChangeEnabled then
			local wheelInput = Util.Clamp(-1, 1, -input.Position.Z)

			local newDistance
			if self.inFirstPerson and wheelInput > 0 then
				newDistance = FIRST_PERSON_DISTANCE_THRESHOLD
			else
				-- The 0.156 and 1.7 values are the slope and intercept of a line that is replacing the old
				-- rk4Integrator function which was not being used as an integrator, only to get a delta as a function of distance,
				-- which was linear as it was being used. These constants preserve the status quo behavior.
				newDistance = self.currentSubjectDistance + 0.156 * self.currentSubjectDistance * wheelInput + 1.7 * math.sign(wheelInput)
			end

			self:SetCameraToSubjectDistance(newDistance)
		end
	end
end

function BaseCamera:UpdateMouseBehavior()
	-- first time transition to first person mode or mouse-locked third person
	if self.inFirstPerson or self.inMouseLockedMode then
		UserGameSettings.RotationType = Enum.RotationType.CameraRelative
		UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
	else
		UserGameSettings.RotationType = Enum.RotationType.MovementRelative
		if self.isRightMouseDown or self.isMiddleMouseDown then
			UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
		else
			UserInputService.MouseBehavior = Enum.MouseBehavior.Default
		end
	end
end

function BaseCamera:UpdateForDistancePropertyChange()
	-- Calling this setter with the current value will force checking that it is still
	-- in range after a change to the min/max distance limits
	self:SetCameraToSubjectDistance(self.currentSubjectDistance)
end

function BaseCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
	local player = Players.LocalPlayer

	local lastSubjectDistance = self.currentSubjectDistance

	-- By default, camera modules will respect LockFirstPerson and override the currentSubjectDistance with 0
	-- regardless of what Player.CameraMinZoomDistance is set to, so that first person can be made
	-- available by the developer without needing to allow players to mousewheel dolly into first person.
	-- Some modules will override this function to remove or change first-person capability.
	if player.CameraMode == Enum.CameraMode.LockFirstPerson then
		self.currentSubjectDistance = 0.5
		if not self.inFirstPerson then
			self:EnterFirstPerson()
		end
	else
		local newSubjectDistance = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, desiredSubjectDistance)
		if newSubjectDistance < FIRST_PERSON_DISTANCE_THRESHOLD then
			self.currentSubjectDistance = 0.5
			if not self.inFirstPerson then
				self:EnterFirstPerson()
			end
		else
			self.currentSubjectDistance = newSubjectDistance
			if self.inFirstPerson then
				self:LeaveFirstPerson()
			end
		end
	end

	-- Pass target distance and zoom direction to the zoom controller
	ZoomController.SetZoomParameters(self.currentSubjectDistance, math.sign(desiredSubjectDistance - lastSubjectDistance))

	-- Returned only for convenience to the caller to know the outcome
	return self.currentSubjectDistance
end

function BaseCamera:SetCameraType( cameraType )
	--Used by derived classes
	self.cameraType = cameraType
end

function BaseCamera:GetCameraType()
	return self.cameraType
end
