SyntaxGameServer/RCCService2021/ExtraContent/scripts/PlayerScripts/StarterPlayerScripts_NewStr.../PlayerModule.module/CameraModule/VehicleCamera.lua

237 lines
7.0 KiB
Lua

local FFlagUserCarCam do
local success, result = pcall(function()
return UserSettings():IsUserFeatureEnabled("UserCarCam")
end)
FFlagUserCarCam = success and result
end
local EPSILON = 1e-3
local PITCH_LIMIT = math.rad(80)
local YAW_DEFAULT = math.rad(0)
local ZOOM_MINIMUM = 0.5
local ZOOM_SENSITIVITY_CURVATURE = 0.5
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local BaseCamera = require(script.Parent:WaitForChild("BaseCamera"))
local CameraInput = require(script.Parent:WaitForChild("CameraInput"))
local CameraUtils = require(script.Parent:WaitForChild("CameraUtils"))
local ZoomController = require(script.Parent:WaitForChild("ZoomController"))
local VehicleCameraCore = require(script:WaitForChild("VehicleCameraCore"))
local VehicleCameraConfig = require(script:WaitForChild("VehicleCameraConfig"))
local localPlayer = Players.LocalPlayer
local map = CameraUtils.map
local Spring = CameraUtils.Spring
local mapClamp = CameraUtils.mapClamp
local sanitizeAngle = CameraUtils.sanitizeAngle
-- pitch-axis rotational velocity of a part with a given CFrame and total RotVelocity
local function pitchVelocity(rotVel, cf)
return math.abs(cf.XVector:Dot(rotVel))
end
-- yaw-axis rotational velocity of a part with a given CFrame and total RotVelocity
local function yawVelocity(rotVel, cf)
return math.abs(cf.YVector:Dot(rotVel))
end
-- track physics solver time delta separately from the render loop to correctly synchronize time delta
local worldDt = 1/60
if FFlagUserCarCam then
RunService.Stepped:Connect(function(_, _worldDt)
worldDt = _worldDt
end)
end
local VehicleCamera = setmetatable({}, BaseCamera)
VehicleCamera.__index = VehicleCamera
function VehicleCamera.new()
assert(FFlagUserCarCam)
local self = setmetatable(BaseCamera.new(), VehicleCamera)
self:Reset()
return self
end
function VehicleCamera:Reset()
assert(FFlagUserCarCam)
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()
assert(FFlagUserCarCam)
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()
assert(FFlagUserCarCam)
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()
assert(FFlagUserCarCam)
-- no-op override; VR transform is not applied in vehicles
end
function VehicleCamera:EnterFirstPerson()
assert(FFlagUserCarCam)
self.inFirstPerson = true
self:UpdateMouseBehavior()
end
function VehicleCamera:LeaveFirstPerson()
assert(FFlagUserCarCam)
self.inFirstPerson = false
self:UpdateMouseBehavior()
end
return VehicleCamera