local T={}T.__index=T local q=tick local function H()return q()*1000 end function T.new(q,H,r)local n=setmetatable({Input=0;Target=0,P=0,I=0;D=0,LastInput=0;ITerm=0;LastError=0;Output=0;SampleTimeMillis=10,LastTime=0;OutMin=-math.huge;OutMax=math.huge,_paused=false},T)n:SetTunings(q,H,r)return n end function T.SetInput(H,T,q)if q then H.Input=math.clamp(T,H.OutMin,H.OutMax)else H.Input=T end end function T.SetTarget(H,T,q)if q then H.Target=math.clamp(T,H.OutMin,H.OutMax)else H.Target=T end end function T.SetTunings(r,T,q,H)r.P=T r.I=q*r.SampleTimeMillis r.D=H/r.SampleTimeMillis end function T.SetSampleTime(q,T)T=math.clamp(T,0)local H=T/q.SampleTimeMillis q.I=q.I*H q.D=q.D/H q.SampleTimeMillis=T end function T.SetOutputLimits(H,T,q)H.OutMin=math.min(T,q)H.OutMax=math.max(T,q)if H.Output>H.OutMax then H.Output=H.OutMax elseif H.Output<H.OutMin then H.Output=H.OutMin end if H.ITerm>H.OutMax then H.ITerm=H.OutMax elseif H.ITerm<H.OutMin then H.ITerm=H.OutMin end end function T.ClearOutputLimits(T)T:SetOutputLimits(-math.huge,math.huge)end function T.Compute(T)local q=H()local r=q-T.LastTime if r>T.SampleTimeMillis then local H=T.Target-T.Input T.ITerm=math.clamp(T.ITerm+T.I*H,T.OutMin,T.OutMax)local r=T.Input-T.LastInput T.Output=math.clamp((T.P*H+T.ITerm)-T.D*r,T.OutMin,T.OutMax)T.LastError=H T.LastInput=T.Input T.LastTime=q end return T.Output end function T._hookupHeartbeat(T)T._heartbeat=(game:GetService("\082\117\110\083\101\114\118\105\099\101")).Heartbeat:Connect(T._callback)end function T.Run(H,T,q)if H._running then error("\065\108\114\101\097\100\121 \114\117\110\110\105\110\103")return end H._running=true H._paused=false H._callback=function(r)T(r)q(H:Compute())end H:_hookupHeartbeat()end function T.Stop(T)if not T._running then return end T._heartbeat:Disconnect()T._heartbeat=nil T._callback=nil T._running=false end function T.Pause(T)if T._running and not T._paused then T._paused=true T._heartbeat:Disconnect()end end function T.Resume(T)if T._running and(T._paused and T._callback)then T.LastInput=T.Input T.ITerm=math.clamp(T.Output,T.OutMin,T.OutMax)T._paused=false T:_hookupHeartbeat()end end function T.Clone(q)local H=T.new(q.P,q.I,q.D)H:SetSampleTime(q.SampleTimeMillis)return H end return T
-- PID
-- Crazyman32
-- July 14, 2018

--[[
	
	PID stands for Proportional-Integral-Derivative. It is common practice to use
	a "PID Controller" (like this one) to calculate the desired output to meet a
	certain setpoint or target. For instance, a car's cruise control setting
	might use a PID Controller to calculate the necessary acceleration needed
	to go from the current speed to the desired speed.
	
	
	pid = PID.new(kp, ki, kd)
	
		VOID    pid:SetInput(input [, clampToMinMax])
		VOID    pid:SetTarget(target [, clampToMinMax])
		
		NUMBER  pid:Compute()
		
		VOID    pid:SetTunings(kp, ki, kd)
		VOID    pid:SetSampleTime(sampleTimeMillis)
		VOID    pid:SetOutputLimits(min, max)
		VOID    pid:ClearOutputLimits()
		
		VOID    pid:Run(callbackBefore, callbackAfter)
		VOID    pid:Stop()
		VOID    pid:Pause()
		VOID    pid:Resume()
		
		VOID    pid:Clone()
		
	
	EXAMPLE:
	
		pid = PID.new(0.01, 0.01, -0.01)
		
		pid:Run(
			function()
				pid:SetInput(currentSpeed)
				pid:SetTarget(desiredSpeed)
			end,
			function(output)
				car:SetAcceleration(output)
			end
		)
	
--]]



local PID = {}
PID.__index = PID


local tick = tick
local function millis()
	return tick() * 1000
end


function PID.new(kp, ki, kd)
	
	local self = setmetatable({
		Input = 0;
		Target = 0;
		P = 0;
		I = 0;
		D = 0;
		LastInput = 0;
		ITerm = 0;
		LastError = 0;
		Output = 0;
		SampleTimeMillis = 10;
		LastTime = 0;
		OutMin = -math.huge;
		OutMax = math.huge;
		_paused = false;
	}, PID)
	
	self:SetTunings(kp, ki, kd)
	
	return self
	
end


function PID:SetInput(input, clampToMinMax)
	if (clampToMinMax) then
		self.Input = math.clamp(input, self.OutMin, self.OutMax)
	else
		self.Input = input
	end
end


function PID:SetTarget(target, clampToMinMax)
	if (clampToMinMax) then
		self.Target = math.clamp(target, self.OutMin, self.OutMax)
	else
		self.Target = target
	end
end


function PID:SetTunings(kp, ki, kd)
	self.P = kp
	self.I = ki * self.SampleTimeMillis
	self.D = kd / self.SampleTimeMillis
end


function PID:SetSampleTime(newSampleTime)
	newSampleTime = math.clamp(newSampleTime, 0)
	local ratio = newSampleTime / self.SampleTimeMillis
	self.I = self.I * ratio
	self.D = self.D / ratio
	self.SampleTimeMillis = newSampleTime
end


function PID:SetOutputLimits(min, max)
	self.OutMin = math.min(min, max)
	self.OutMax = math.max(min, max)
	if (self.Output > self.OutMax) then
		self.Output = self.OutMax
	elseif (self.Output < self.OutMin) then
		self.Output = self.OutMin
	end
	if (self.ITerm > self.OutMax) then
		self.ITerm = self.OutMax
	elseif (self.ITerm < self.OutMin) then
		self.ITerm = self.OutMin
	end
end


function PID:ClearOutputLimits()
	self:SetOutputLimits(-math.huge, math.huge)
end


function PID:Compute()
	
	local now = millis()
	local timeChange = (now - self.LastTime)
	
	if (timeChange > self.SampleTimeMillis) then
	
		local err = self.Target - self.Input
		self.ITerm = math.clamp((self.ITerm + (self.I * err)), self.OutMin, self.OutMax)
		local dInput = (self.Input - self.LastInput)
		
		self.Output = math.clamp((self.P * err) + (self.ITerm) - (self.D * dInput), self.OutMin, self.OutMax)
		
		self.LastError = err
		self.LastInput = self.Input
		self.LastTime = now
		
	end
	
	return self.Output
	
end


function PID:_hookupHeartbeat()
	self._heartbeat = game:GetService("RunService").Heartbeat:Connect(self._callback)
end


function PID:Run(callbackBefore, callbackAfter)
	if (self._running) then
		error("Already running")
		return
	end
	self._running = true
	self._paused = false
	self._callback = function(dt)
		callbackBefore(dt)
		callbackAfter(self:Compute())
	end
	self:_hookupHeartbeat()
end


function PID:Stop()
	if (not self._running) then return end
	self._heartbeat:Disconnect()
	self._heartbeat = nil
	self._callback = nil
	self._running = false
end


function PID:Pause()
	if (self._running and not self._paused) then
		self._paused = true
		self._heartbeat:Disconnect()
	end
end


function PID:Resume()
	if (self._running and self._paused and self._callback) then
		self.LastInput = self.Input
		self.ITerm = math.clamp(self.Output, self.OutMin, self.OutMax)
		self._paused = false
		self:_hookupHeartbeat()
	end
end


function PID:Clone()
	local clone = PID.new(self.P, self.I, self.D)
	clone:SetSampleTime(self.SampleTimeMillis)
	return clone
end


return PID