128 lines
3.2 KiB
Lua
128 lines
3.2 KiB
Lua
-- Zoom
|
|
-- Controls the distance between the focus and the camera.
|
|
|
|
local ZOOM_STIFFNESS = 4.5
|
|
local ZOOM_DEFAULT = 12.5
|
|
local ZOOM_ACCELERATION = 0.0375
|
|
|
|
local MIN_FOCUS_DIST = 0.5
|
|
local DIST_OPAQUE = 1
|
|
|
|
local Popper = require(script:WaitForChild("Popper"))
|
|
|
|
local clamp = math.clamp
|
|
local exp = math.exp
|
|
local min = math.min
|
|
local max = math.max
|
|
local pi = math.pi
|
|
|
|
local cameraMinZoomDistance, cameraMaxZoomDistance do
|
|
local Player = game:GetService("Players").LocalPlayer
|
|
|
|
local function updateBounds()
|
|
cameraMinZoomDistance = Player.CameraMinZoomDistance
|
|
cameraMaxZoomDistance = Player.CameraMaxZoomDistance
|
|
end
|
|
|
|
updateBounds()
|
|
|
|
Player:GetPropertyChangedSignal("CameraMinZoomDistance"):Connect(updateBounds)
|
|
Player:GetPropertyChangedSignal("CameraMaxZoomDistance"):Connect(updateBounds)
|
|
end
|
|
|
|
local ConstrainedSpring = {} do
|
|
ConstrainedSpring.__index = ConstrainedSpring
|
|
|
|
function ConstrainedSpring.new(freq, x, minValue, maxValue)
|
|
x = clamp(x, minValue, maxValue)
|
|
return setmetatable({
|
|
freq = freq, -- Undamped frequency (Hz)
|
|
x = x, -- Current position
|
|
v = 0, -- Current velocity
|
|
minValue = minValue, -- Minimum bound
|
|
maxValue = maxValue, -- Maximum bound
|
|
goal = x, -- Goal position
|
|
}, ConstrainedSpring)
|
|
end
|
|
|
|
function ConstrainedSpring:Step(dt)
|
|
local freq = self.freq*2*pi -- Convert from Hz to rad/s
|
|
local x = self.x
|
|
local v = self.v
|
|
local minValue = self.minValue
|
|
local maxValue = self.maxValue
|
|
local goal = self.goal
|
|
|
|
-- Solve the spring ODE for position and velocity after time t, assuming critical damping:
|
|
-- 2*f*x'[t] + x''[t] = f^2*(g - x[t])
|
|
-- Knowns are x[0] and x'[0].
|
|
-- Solve for x[t] and x'[t].
|
|
|
|
local offset = goal - x
|
|
local step = freq*dt
|
|
local decay = exp(-step)
|
|
|
|
local x1 = goal + (v*dt - offset*(step + 1))*decay
|
|
local v1 = ((offset*freq - v)*step + v)*decay
|
|
|
|
-- Constrain
|
|
if x1 < minValue then
|
|
x1 = minValue
|
|
v1 = 0
|
|
elseif x1 > maxValue then
|
|
x1 = maxValue
|
|
v1 = 0
|
|
end
|
|
|
|
self.x = x1
|
|
self.v = v1
|
|
|
|
return x1
|
|
end
|
|
end
|
|
|
|
local zoomSpring = ConstrainedSpring.new(ZOOM_STIFFNESS, ZOOM_DEFAULT, MIN_FOCUS_DIST, cameraMaxZoomDistance)
|
|
|
|
local function stepTargetZoom(z, dz, zoomMin, zoomMax)
|
|
z = clamp(z + dz*(1 + z*ZOOM_ACCELERATION), zoomMin, zoomMax)
|
|
if z < DIST_OPAQUE then
|
|
z = dz <= 0 and zoomMin or DIST_OPAQUE
|
|
end
|
|
return z
|
|
end
|
|
|
|
local zoomDelta = 0
|
|
|
|
local Zoom = {} do
|
|
function Zoom.Update(renderDt, focus, extrapolation)
|
|
local poppedZoom = math.huge
|
|
|
|
if zoomSpring.goal > DIST_OPAQUE then
|
|
-- Make a pessimistic estimate of zoom distance for this step without accounting for poppercam
|
|
local maxPossibleZoom = max(
|
|
zoomSpring.x,
|
|
stepTargetZoom(zoomSpring.goal, zoomDelta, cameraMinZoomDistance, cameraMaxZoomDistance)
|
|
)
|
|
|
|
-- Run the Popper algorithm on the feasible zoom range, [MIN_FOCUS_DIST, maxPossibleZoom]
|
|
poppedZoom = Popper(
|
|
focus*CFrame.new(0, 0, MIN_FOCUS_DIST),
|
|
maxPossibleZoom - MIN_FOCUS_DIST,
|
|
extrapolation
|
|
) + MIN_FOCUS_DIST
|
|
end
|
|
|
|
zoomSpring.minValue = MIN_FOCUS_DIST
|
|
zoomSpring.maxValue = min(cameraMaxZoomDistance, poppedZoom)
|
|
|
|
return zoomSpring:Step(renderDt)
|
|
end
|
|
|
|
function Zoom.SetZoomParameters(targetZoom, newZoomDelta)
|
|
zoomSpring.goal = targetZoom
|
|
zoomDelta = newZoomDelta
|
|
end
|
|
end
|
|
|
|
return Zoom
|