local L=require(script.Parent.Parent.CameraUtils)local G=require(script.Parent.VehicleCameraConfig)local Y=L.map local U=L.mapClamp local Z=L.sanitizeAngle local function i(L)local G,Y=L:toEulerAnglesYXZ()return Z(Y)end local function B(L)local G=L:toEulerAnglesYXZ()return Z(G)end local function p(L,G,Y,U,i)local B=Z(U-Y)local p=math.exp(-G*L)local j=Z((B*(1+G*L)+i*L)*p+Y)local D=(i*(1-G*L)-B*((G*G)*L))*p return j,D end local j={}do j.__index=j function j.new(L,G,Y)return setmetatable({fRising=L,fFalling=G;g=Y;p=Y,v=Y*0},j)end function j.step(G,L)local Y=G.fRising local U=G.fFalling local Z=G.g local i=G.p local B=G.v local p=(2*math.pi)*(B>0 and Y or U)local j=i-Z local D=math.exp(-p*L)local a=(j*(1+p*L)+B*L)*D+Z local t=(B*(1-p*L)-j*((p*p)*L))*D G.p=a G.v=t return a end end local D={}do D.__index=D function D.new(L)assert(typeof(L)=="\067\070\114\097\109\101")return setmetatable({yawG=i(L),yawP=i(L);yawV=0,pitchG=B(L);pitchP=B(L);pitchV=0,fSpringYaw=j.new(G.yawReponseDampingRising,G.yawResponseDampingFalling,0),fSpringPitch=j.new(G.pitchReponseDampingRising,G.pitchResponseDampingFalling,0)},D)end function D.setGoal(G,L)assert(typeof(L)=="\067\070\114\097\109\101")G.yawG=i(L)G.pitchG=B(L)end function D.getCFrame(L)return CFrame.fromEulerAnglesYXZ(L.pitchP,L.yawP,0)end function D.step(j,L,Z,i,B)assert(typeof(L)=="\110\117\109\098\101\114")assert(typeof(i)=="\110\117\109\098\101\114")assert(typeof(Z)=="\110\117\109\098\101\114")assert(typeof(B)=="\110\117\109\098\101\114")local D=j.fSpringYaw local a=j.fSpringPitch D.g=U(Y(B,0,1,i,0),math.rad(G.cutoffMinAngularVelYaw),math.rad(G.cutoffMaxAngularVelYaw),1,0)a.g=U(Y(B,0,1,Z,0),math.rad(G.cutoffMinAngularVelPitch),math.rad(G.cutoffMaxAngularVelPitch),1,0)local t=((2*math.pi)*G.yawStiffness)*D:step(L)local c=((2*math.pi)*G.pitchStiffness)*a:step(L)c*=Y(B,0,1,1,G.firstPersonResponseMul)t*=Y(B,0,1,1,G.firstPersonResponseMul)j.yawP,j.yawV=p(L,t,j.yawG,j.yawP,j.yawV)j.pitchP,j.pitchV=p(L,c,j.pitchG,j.pitchP,j.pitchV)return j:getCFrame()end end local a={}do a.__index=a function a.new(L)return setmetatable({vrs=D.new(L)},a)end function a.step(Z,L,G,Y,U)return Z.vrs:step(L,G,Y,U)end function a.setTransform(G,L)G.vrs:setGoal(L)end end return a
--!nonstrict
local CameraUtils = require(script.Parent.Parent.CameraUtils)
local VehicleCameraConfig = require(script.Parent.VehicleCameraConfig)

local map = CameraUtils.map
local mapClamp = CameraUtils.mapClamp
local sanitizeAngle = CameraUtils.sanitizeAngle

-- extract sanitized yaw from a CFrame rotation
local function getYaw(cf)
	local _, yaw = cf:toEulerAnglesYXZ()
	return sanitizeAngle(yaw)
end

-- extract sanitized pitch from a CFrame rotation
local function getPitch(cf)
	local pitch = cf:toEulerAnglesYXZ()
	return sanitizeAngle(pitch)
end

-- step a damped angular spring axis
local function stepSpringAxis(dt, f, g, p, v)
	local offset = sanitizeAngle(p - g)
	local decay = math.exp(-f*dt)

	local p1 = sanitizeAngle((offset*(1 + f*dt) + v*dt)*decay + g)
	local v1 = (v*(1 - f*dt) - offset*(f*f*dt))*decay

	return p1, v1
end

-- value damper with separate response frequencies for rising and falling values
local VariableEdgeSpring = {} do
	VariableEdgeSpring.__index = VariableEdgeSpring

	function VariableEdgeSpring.new(fRising, fFalling, position)
		return setmetatable({
			fRising = fRising,
			fFalling = fFalling,
			g = position,
			p = position,
			v = position*0,
		}, VariableEdgeSpring)
	end

	function VariableEdgeSpring:step(dt)
		local fRising = self.fRising
		local fFalling = self.fFalling
		local g = self.g
		local p0 = self.p
		local v0 = self.v

		local f = 2*math.pi*(v0 > 0 and fRising or fFalling)

		local offset = p0 - g
		local decay = math.exp(-f*dt)

		local p1 = (offset*(1 + f*dt) + v0*dt)*decay + g
		local v1 = (v0*(1 - f*dt) - offset*(f*f*dt))*decay

		self.p = p1
		self.v = v1

		return p1
	end
end

-- damps a 3D rotation in Tait-Bryan YXZ space, filtering out Z
local YawPitchSpring = {} do
	YawPitchSpring.__index = YawPitchSpring

	function YawPitchSpring.new(cf)
		assert(typeof(cf) == "CFrame")
		
		return setmetatable({
			yawG = getYaw(cf), -- yaw goal
			yawP = getYaw(cf), -- yaw position
			yawV = 0, -- yaw velocity

			pitchG = getPitch(cf), -- pitch goal
			pitchP = getPitch(cf), -- pitch position
			pitchV = 0, -- pitch velocity

			-- yaw/pitch response springs
			fSpringYaw = VariableEdgeSpring.new(
				VehicleCameraConfig.yawReponseDampingRising,
				VehicleCameraConfig.yawResponseDampingFalling,
				0
			),
			fSpringPitch = VariableEdgeSpring.new(
				VehicleCameraConfig.pitchReponseDampingRising,
				VehicleCameraConfig.pitchResponseDampingFalling,
				0
			),
		}, YawPitchSpring)
	end
	
	-- Extract Tait-Bryan angles from a CFrame rotation
	function YawPitchSpring:setGoal(goalCFrame)
		assert(typeof(goalCFrame) == "CFrame")
		
		self.yawG = getYaw(goalCFrame)
		self.pitchG = getPitch(goalCFrame)
	end

	function YawPitchSpring:getCFrame()
		return CFrame.fromEulerAnglesYXZ(self.pitchP, self.yawP, 0)
	end

	function YawPitchSpring:step(dt, pitchVel, yawVel, firstPerson)
		assert(typeof(dt) == "number")
		assert(typeof(yawVel) == "number")
		assert(typeof(pitchVel) == "number")
		assert(typeof(firstPerson) == "number")
		
		local fSpringYaw = self.fSpringYaw
		local fSpringPitch = self.fSpringPitch
		
		-- calculate the frequency spring
		fSpringYaw.g = mapClamp(
			map(firstPerson, 0, 1, yawVel, 0),
			math.rad(VehicleCameraConfig.cutoffMinAngularVelYaw),
			math.rad(VehicleCameraConfig.cutoffMaxAngularVelYaw),
			1, 0
		)

		fSpringPitch.g = mapClamp(
			map(firstPerson, 0, 1, pitchVel, 0),
			math.rad(VehicleCameraConfig.cutoffMinAngularVelPitch),
			math.rad(VehicleCameraConfig.cutoffMaxAngularVelPitch),
			1, 0
		)
		
		-- calculate final frequencies
		local fYaw = 2*math.pi*VehicleCameraConfig.yawStiffness*fSpringYaw:step(dt)
		local fPitch = 2*math.pi*VehicleCameraConfig.pitchStiffness*fSpringPitch:step(dt)
		
		-- adjust response for first person
		fPitch *= map(firstPerson, 0, 1, 1, VehicleCameraConfig.firstPersonResponseMul)
		fYaw *= map(firstPerson, 0, 1, 1, VehicleCameraConfig.firstPersonResponseMul)
		
		-- step yaw
		self.yawP, self.yawV = stepSpringAxis(
			dt,
			fYaw,
			self.yawG,
			self.yawP,
			self.yawV
		)
		
		-- step pitch
		self.pitchP, self.pitchV = stepSpringAxis(
			dt,
			fPitch,
			self.pitchG,
			self.pitchP,
			self.pitchV
		)

		return self:getCFrame()
	end
end

local VehicleCameraCore = {} do
	VehicleCameraCore.__index = VehicleCameraCore

	function VehicleCameraCore.new(transform)
		return setmetatable({
			vrs = YawPitchSpring.new(transform)
		}, VehicleCameraCore)
	end

	function VehicleCameraCore:step(dt, pitchVel, yawVel, firstPerson)
		return self.vrs:step(dt, pitchVel, yawVel, firstPerson)
	end

	function VehicleCameraCore:setTransform(transform)
		self.vrs:setGoal(transform)
	end
end

return VehicleCameraCore
