local Y=.016666666666667 RunService.Stepped:Connect(function(Q,c)Y=c end)local Q=setmetatable({},BaseCamera)Q.__index=Q function Q.new()local Y=setmetatable(BaseCamera.new(),Q)Y:Reset()return Y end function Q.Reset(Y)Y.vehicleCameraCore=VehicleCameraCore.new(Y:GetSubjectCFrame())Y.pitchSpring=Spring.new(0,-math.rad(VehicleCameraConfig.pitchBaseAngle))Y.yawSpring=Spring.new(0,YAW_DEFAULT)Y.lastPanTick=0 local Q=workspace.CurrentCamera local c=Q and Q.CameraSubject assert(Q)assert(c)assert(c:IsA("\086\101\104\105\099\108\101\083\101\097\116"))local T=c:GetConnectedParts(true)local M,B=CameraUtils.getLooseBoundingSphere(T)B=math.max(B,EPSILON)Y.assemblyRadius=B Y.assemblyOffset=c.CFrame:Inverse()*M Y:_StepInitialZoom()end function Q._StepInitialZoom(Y)Y:SetCameraToSubjectDistance(math.max(ZoomController.GetZoomRadius(),Y.assemblyRadius*VehicleCameraConfig.initialZoomRadiusMul))end function Q._StepRotation(c,Y,Q)local T=c.yawSpring local M=c.pitchSpring local B=CameraInput.getRotation(true)local e=-B.X local x=-B.Y T.pos=sanitizeAngle(T.pos+e)M.pos=sanitizeAngle(math.clamp(M.pos+x,-PITCH_LIMIT,PITCH_LIMIT))if CameraInput.getRotationActivated()then c.lastPanTick=os.clock()end local p=-math.rad(VehicleCameraConfig.pitchBaseAngle)local C=math.rad(VehicleCameraConfig.pitchDeadzoneAngle)if os.clock()-c.lastPanTick>VehicleCameraConfig.autocorrectDelay then local Y=mapClamp(Q,VehicleCameraConfig.autocorrectMinCarSpeed,VehicleCameraConfig.autocorrectMaxCarSpeed,0,VehicleCameraConfig.autocorrectResponse)T.freq=Y M.freq=Y if T.freq<EPSILON then T.vel=0 end if M.freq<EPSILON then M.vel=0 end if math.abs(sanitizeAngle(p-M.pos))<=C then M.goal=M.pos else M.goal=p end else T.freq=0 T.vel=0 M.freq=0 M.vel=0 M.goal=p end return CFrame.fromEulerAnglesYXZ(M:step(Y),T:step(Y),0)end function Q._GetThirdPersonLocalOffset(Y)return Y.assemblyOffset+Vector3.new(0,Y.assemblyRadius*VehicleCameraConfig.verticalCenterOffset,0)end function Q._GetFirstPersonLocalOffset(Q,Y)local c=localPlayer.Character if c and c.Parent then local Q=c:FindFirstChild("\072\101\097\100")if Q and Q:IsA("\066\097\115\101\080\097\114\116")then return Y:Inverse()*Q.Position end end return Q:_GetThirdPersonLocalOffset()end function Q.Update(Q)local c=workspace.CurrentCamera local T=c and c.CameraSubject local M=Q.vehicleCameraCore assert(c)assert(T)assert(T:IsA("\086\101\104\105\099\108\101\083\101\097\116"))local B=Y Y=0 local e=Q:GetSubjectCFrame()local x=Q:GetSubjectVelocity()local p=Q:GetSubjectRotVelocity()local C=math.abs(x:Dot(e.ZVector))local E=yawVelocity(p,e)local t=pitchVelocity(p,e)local s=Q:StepZoom()local f=Q:_StepRotation(B,C)local H=mapClamp(s,ZOOM_MINIMUM,Q.assemblyRadius,1,0)local i=Q:_GetThirdPersonLocalOffset()local g=Q:_GetFirstPersonLocalOffset(e)local J=i:Lerp(g,H)M:setTransform(e)local l=M:step(B,t,E,H)local I=(CFrame.new(e*J)*l)*f local k=I*CFrame.new(0,0,s)return k,I end function Q.ApplyVRTransform(Y) end function Q.EnterFirstPerson(Y)Y.inFirstPerson=true Y:UpdateMouseBehavior()end function Q.LeaveFirstPerson(Y)Y.inFirstPerson=false Y:UpdateMouseBehavior()end return Q

local worldDt = 1/60
RunService.Stepped:Connect(function(_, _worldDt)
	worldDt = _worldDt
end)

local VehicleCamera = setmetatable({}, BaseCamera)
VehicleCamera.__index = VehicleCamera

function VehicleCamera.new()
	local self = setmetatable(BaseCamera.new(), VehicleCamera)
	self:Reset()
	return self
end

function VehicleCamera:Reset()
	self.vehicleCameraCore = VehicleCameraCore.new(self:GetSubjectCFrame())
	self.pitchSpring = Spring.new(0, -math.rad(VehicleCameraConfig.pitchBaseAngle))
	self.yawSpring = Spring.new(0, YAW_DEFAULT)
	self.lastPanTick = 0
	
	local camera = workspace.CurrentCamera
	local cameraSubject = camera and camera.CameraSubject
	
	assert(camera)
	assert(cameraSubject)
	assert(cameraSubject:IsA("VehicleSeat"))
	
	local assemblyParts = cameraSubject:GetConnectedParts(true) -- passing true to recursively get all assembly parts
	local assemblyPosition, assemblyRadius = CameraUtils.getLooseBoundingSphere(assemblyParts)
	
	assemblyRadius = math.max(assemblyRadius, EPSILON)
	
	self.assemblyRadius = assemblyRadius
	self.assemblyOffset = cameraSubject.CFrame:Inverse()*assemblyPosition -- seat-space offset of the assembly bounding sphere center
	
	self:_StepInitialZoom()
end

function VehicleCamera:_StepInitialZoom()
	self:SetCameraToSubjectDistance(math.max(
		ZoomController.GetZoomRadius(),
		self.assemblyRadius*VehicleCameraConfig.initialZoomRadiusMul
	))
end

function VehicleCamera:_StepRotation(dt, vdotz)
	local yawSpring = self.yawSpring
	local pitchSpring = self.pitchSpring
	
	local rotationInput = CameraInput.getRotation(true)
	local dYaw = -rotationInput.X
	local dPitch = -rotationInput.Y
	
	yawSpring.pos = sanitizeAngle(yawSpring.pos + dYaw)
	pitchSpring.pos = sanitizeAngle(math.clamp(pitchSpring.pos + dPitch, -PITCH_LIMIT, PITCH_LIMIT))

	if CameraInput.getRotationActivated() then
		self.lastPanTick = os.clock()
	end

	local pitchBaseAngle = -math.rad(VehicleCameraConfig.pitchBaseAngle)
	local pitchDeadzoneAngle = math.rad(VehicleCameraConfig.pitchDeadzoneAngle)

	if os.clock() - self.lastPanTick > VehicleCameraConfig.autocorrectDelay then
		-- adjust autocorrect response based on forward velocity
		local autocorrectResponse = mapClamp(
			vdotz,
			VehicleCameraConfig.autocorrectMinCarSpeed,
			VehicleCameraConfig.autocorrectMaxCarSpeed,
			0,
			VehicleCameraConfig.autocorrectResponse
		)

		yawSpring.freq = autocorrectResponse
		pitchSpring.freq = autocorrectResponse
		
		-- zero out response under a threshold
		if yawSpring.freq < EPSILON then
			yawSpring.vel = 0
		end

		if pitchSpring.freq < EPSILON then
			pitchSpring.vel = 0
		end

		if math.abs(sanitizeAngle(pitchBaseAngle - pitchSpring.pos)) <= pitchDeadzoneAngle then
			-- do nothing within the deadzone
			pitchSpring.goal = pitchSpring.pos
		else
			pitchSpring.goal = pitchBaseAngle
		end
	else
		yawSpring.freq = 0
		yawSpring.vel = 0

		pitchSpring.freq = 0
		pitchSpring.vel = 0

		pitchSpring.goal = pitchBaseAngle
	end

	return CFrame.fromEulerAnglesYXZ(
		pitchSpring:step(dt),
		yawSpring:step(dt),
		0
	)
end

function VehicleCamera:_GetThirdPersonLocalOffset()
	return self.assemblyOffset + Vector3.new(0, self.assemblyRadius*VehicleCameraConfig.verticalCenterOffset, 0)
end

function VehicleCamera:_GetFirstPersonLocalOffset(subjectCFrame)
	local character = localPlayer.Character
	
	if character and character.Parent then
		local head = character:FindFirstChild("Head")
		
		if head and head:IsA("BasePart") then
			return subjectCFrame:Inverse()*head.Position
		end
	end
	
	return self:_GetThirdPersonLocalOffset()
end

function VehicleCamera:Update()
	local camera = workspace.CurrentCamera
	local cameraSubject = camera and camera.CameraSubject
	local vehicleCameraCore = self.vehicleCameraCore
	
	assert(camera)
	assert(cameraSubject)
	assert(cameraSubject:IsA("VehicleSeat"))
	
	-- consume the physics solver time delta to account for mismatched physics/render cycles
	local dt = worldDt
	worldDt = 0
	
	-- get subject info
	local subjectCFrame = self:GetSubjectCFrame()
	local subjectVel = self:GetSubjectVelocity()
	local subjectRotVel = self:GetSubjectRotVelocity()
	
	-- measure the local-to-world-space forward velocity of the vehicle
	local vDotZ = math.abs(subjectVel:Dot(subjectCFrame.ZVector))
	local yawVel = yawVelocity(subjectRotVel, subjectCFrame)
	local pitchVel = pitchVelocity(subjectRotVel, subjectCFrame)
	
	-- step camera components forward
	local zoom = self:StepZoom()
	local objectRotation = self:_StepRotation(dt, vDotZ)
	
	-- mix third and first person offsets in local space
	local firstPerson = mapClamp(zoom, ZOOM_MINIMUM, self.assemblyRadius, 1, 0)
	
	local tpOffset = self:_GetThirdPersonLocalOffset()
	local fpOffset = self:_GetFirstPersonLocalOffset(subjectCFrame)
	local localOffset = tpOffset:Lerp(fpOffset, firstPerson)
	
	-- step core forward
	vehicleCameraCore:setTransform(subjectCFrame)
	local processedRotation = vehicleCameraCore:step(dt, pitchVel, yawVel, firstPerson)
	
	-- calculate final focus & cframe
	local focus = CFrame.new(subjectCFrame*localOffset)*processedRotation*objectRotation
	local cf = focus*CFrame.new(0, 0, zoom)

	return cf, focus
end

function VehicleCamera:ApplyVRTransform()
	-- no-op override; VR transform is not applied in vehicles
end

function VehicleCamera:EnterFirstPerson()
	self.inFirstPerson = true
	self:UpdateMouseBehavior()
end

function VehicleCamera:LeaveFirstPerson()
	self.inFirstPerson = false
	self:UpdateMouseBehavior()
end

return VehicleCamera
