-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathCameraUtils.lua
More file actions
309 lines (253 loc) · 8.8 KB
/
CameraUtils.lua
File metadata and controls
309 lines (253 loc) · 8.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
--[[
CameraUtils - Math utility functions shared by multiple camera scripts
2018 Camera Update - AllYourBlox
--]]
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local UserGameSettings = UserSettings():GetService("UserGameSettings")
local CameraUtils = {}
local function round(num: number)
return math.floor(num + 0.5)
end
-- Critically damped spring class for fluid motion effects
local Spring = {} do
Spring.__index = Spring
-- Initialize to a given undamped frequency and default position
function Spring.new(freq, pos)
return setmetatable({
freq = freq,
goal = pos,
pos = pos,
vel = 0,
}, Spring)
end
-- Advance the spring simulation by `dt` seconds
function Spring:step(dt: number)
local f: number = self.freq::number * 2.0 * math.pi
local g: Vector3 = self.goal
local p0: Vector3 = self.pos
local v0: Vector3 = self.vel
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.pos = p1
self.vel = v1
return p1
end
end
CameraUtils.Spring = Spring
-- map a value from one range to another
function CameraUtils.map(x: number, inMin: number, inMax: number, outMin: number, outMax: number): number
return (x - inMin)*(outMax - outMin)/(inMax - inMin) + outMin
end
-- maps a value from one range to another, clamping to the output range. order does not matter
function CameraUtils.mapClamp(x: number, inMin: number, inMax: number, outMin: number, outMax: number): number
return math.clamp(
(x - inMin)*(outMax - outMin)/(inMax - inMin) + outMin,
math.min(outMin, outMax),
math.max(outMin, outMax)
)
end
-- Ritter's loose bounding sphere algorithm
function CameraUtils.getLooseBoundingSphere(parts: {BasePart})
local points = table.create(#parts)
for idx, part in pairs(parts) do
points[idx] = part.Position
end
-- pick an arbitrary starting point
local x = points[1]
-- get y, the point furthest from x
local y = x
local yDist = 0
for _, p in ipairs(points) do
local pDist = (p - x).Magnitude
if pDist > yDist then
y = p
yDist = pDist
end
end
-- get z, the point furthest from y
local z = y
local zDist = 0
for _, p in ipairs(points) do
local pDist = (p - y).Magnitude
if pDist > zDist then
z = p
zDist = pDist
end
end
-- use (y, z) as the initial bounding sphere
local sc = (y + z)*0.5
local sr = (y - z).Magnitude*0.5
-- expand sphere to fit any outlying points
for _, p in ipairs(points) do
local pDist = (p - sc).Magnitude
if pDist > sr then
-- shift to midpoint
sc = sc + (pDist - sr)*0.5*(p - sc).Unit
-- expand
sr = (pDist + sr)*0.5
end
end
return sc, sr
end
-- canonicalize an angle to +-180 degrees
function CameraUtils.sanitizeAngle(a: number): number
return (a + math.pi)%(2*math.pi) - math.pi
end
-- From TransparencyController
function CameraUtils.Round(num: number, places: number): number
local decimalPivot = 10^places
return math.floor(num * decimalPivot + 0.5) / decimalPivot
end
function CameraUtils.IsFinite(val: number): boolean
return val == val and val ~= math.huge and val ~= -math.huge
end
function CameraUtils.IsFiniteVector3(vec3: Vector3): boolean
return CameraUtils.IsFinite(vec3.X) and CameraUtils.IsFinite(vec3.Y) and CameraUtils.IsFinite(vec3.Z)
end
-- Legacy implementation renamed
function CameraUtils.GetAngleBetweenXZVectors(v1: Vector3, v2: Vector3): number
return math.atan2(v2.X*v1.Z-v2.Z*v1.X, v2.X*v1.X+v2.Z*v1.Z)
end
function CameraUtils.RotateVectorByAngleAndRound(camLook: Vector3, rotateAngle: number, roundAmount: number): number
if camLook.Magnitude > 0 then
camLook = camLook.Unit
local currAngle = math.atan2(camLook.Z, camLook.X)
local newAngle = round((math.atan2(camLook.Z, camLook.X) + rotateAngle) / roundAmount) * roundAmount
return newAngle - currAngle
end
return 0
end
-- K is a tunable parameter that changes the shape of the S-curve
-- the larger K is the more straight/linear the curve gets
local k = 0.35
local lowerK = 0.8
local function SCurveTranform(t: number)
t = math.clamp(t, -1, 1)
if t >= 0 then
return (k*t) / (k - t + 1)
end
return -((lowerK*-t) / (lowerK + t + 1))
end
local DEADZONE = 0.1
local function toSCurveSpace(t: number)
return (1 + DEADZONE) * (2*math.abs(t) - 1) - DEADZONE
end
local function fromSCurveSpace(t: number)
return t/2 + 0.5
end
function CameraUtils.GamepadLinearToCurve(thumbstickPosition: Vector2)
local function onAxis(axisValue)
local sign = 1
if axisValue < 0 then
sign = -1
end
local point = fromSCurveSpace(SCurveTranform(toSCurveSpace(math.abs(axisValue))))
point = point * sign
return math.clamp(point, -1, 1)
end
return Vector2.new(onAxis(thumbstickPosition.X), onAxis(thumbstickPosition.Y))
end
-- This function converts 4 different, redundant enumeration types to one standard so the values can be compared
function CameraUtils.ConvertCameraModeEnumToStandard(enumValue:
Enum.TouchCameraMovementMode |
Enum.ComputerCameraMovementMode |
Enum.DevTouchCameraMovementMode |
Enum.DevComputerCameraMovementMode): Enum.ComputerCameraMovementMode | Enum.DevComputerCameraMovementMode
if enumValue == Enum.TouchCameraMovementMode.Default then
return Enum.ComputerCameraMovementMode.Follow
end
if enumValue == Enum.ComputerCameraMovementMode.Default then
return Enum.ComputerCameraMovementMode.Classic
end
if enumValue == Enum.TouchCameraMovementMode.Classic or
enumValue == Enum.DevTouchCameraMovementMode.Classic or
enumValue == Enum.DevComputerCameraMovementMode.Classic or
enumValue == Enum.ComputerCameraMovementMode.Classic then
return Enum.ComputerCameraMovementMode.Classic
end
if enumValue == Enum.TouchCameraMovementMode.Follow or
enumValue == Enum.DevTouchCameraMovementMode.Follow or
enumValue == Enum.DevComputerCameraMovementMode.Follow or
enumValue == Enum.ComputerCameraMovementMode.Follow then
return Enum.ComputerCameraMovementMode.Follow
end
if enumValue == Enum.TouchCameraMovementMode.Orbital or
enumValue == Enum.DevTouchCameraMovementMode.Orbital or
enumValue == Enum.DevComputerCameraMovementMode.Orbital or
enumValue == Enum.ComputerCameraMovementMode.Orbital then
return Enum.ComputerCameraMovementMode.Orbital
end
if enumValue == Enum.ComputerCameraMovementMode.CameraToggle or
enumValue == Enum.DevComputerCameraMovementMode.CameraToggle then
return Enum.ComputerCameraMovementMode.CameraToggle
end
-- Note: Only the Dev versions of the Enums have UserChoice as an option
if enumValue == Enum.DevTouchCameraMovementMode.UserChoice or
enumValue == Enum.DevComputerCameraMovementMode.UserChoice then
return Enum.DevComputerCameraMovementMode.UserChoice
end
-- For any unmapped options return Classic camera
return Enum.ComputerCameraMovementMode.Classic
end
local function getMouse()
local localPlayer = Players.LocalPlayer
if not localPlayer then
Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
localPlayer = Players.LocalPlayer
end
assert(localPlayer)
return localPlayer:GetMouse()
end
local savedMouseIcon: string = ""
local lastMouseIconOverride: string? = nil
function CameraUtils.setMouseIconOverride(icon: string)
local mouse = getMouse()
-- Only save the icon if it was written by another script.
if mouse.Icon ~= lastMouseIconOverride then
savedMouseIcon = mouse.Icon
end
mouse.Icon = icon
lastMouseIconOverride = icon
end
function CameraUtils.restoreMouseIcon()
local mouse = getMouse()
-- Only restore if it wasn't overwritten by another script.
if mouse.Icon == lastMouseIconOverride then
mouse.Icon = savedMouseIcon
end
lastMouseIconOverride = nil
end
local savedMouseBehavior: Enum.MouseBehavior = Enum.MouseBehavior.Default
local lastMouseBehaviorOverride: Enum.MouseBehavior? = nil
function CameraUtils.setMouseBehaviorOverride(value: Enum.MouseBehavior)
if UserInputService.MouseBehavior ~= lastMouseBehaviorOverride then
savedMouseBehavior = UserInputService.MouseBehavior
end
UserInputService.MouseBehavior = value
lastMouseBehaviorOverride = value
end
function CameraUtils.restoreMouseBehavior()
if UserInputService.MouseBehavior == lastMouseBehaviorOverride then
UserInputService.MouseBehavior = savedMouseBehavior
end
lastMouseBehaviorOverride = nil
end
local savedRotationType: Enum.RotationType = Enum.RotationType.MovementRelative
local lastRotationTypeOverride: Enum.RotationType? = nil
function CameraUtils.setRotationTypeOverride(value: Enum.RotationType)
if UserGameSettings.RotationType ~= lastRotationTypeOverride then
savedRotationType = UserGameSettings.RotationType
end
UserGameSettings.RotationType = value
lastRotationTypeOverride = value
end
function CameraUtils.restoreRotationType()
if UserGameSettings.RotationType == lastRotationTypeOverride then
UserGameSettings.RotationType = savedRotationType
end
lastRotationTypeOverride = nil
end
return CameraUtils