├── FastCameraSystem.rbxm ├── License.md ├── README.md └── src ├── CameraModes.lua ├── CameraSystem.lua ├── Configs.Lua └── FasterCameraModes.lua /FastCameraSystem.rbxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/4thAxiz/Fast-Camera-Systems-Outdated-/2ddacaf655e9134420753db7403d0ecbe04ae512/FastCameraSystem.rbxm -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 4thAxis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Disclaimer, outdated optimizations. Use for the intention of functionality! 2 | ------------------------------------- 3 | # Fast-Camera-Systems 4 | 5 | Probably the most optimized and fastest Camera you'll find written for the Roblox engine-- pretty easy to use... 6 | 7 | # Usage 8 | 9 | ```lua 10 | local Module = require(script:WaitForChild("CameraSystem")) -- CameraModes should be placed under this. 11 | Module.EnableTopDownCamera() -- Functions are pretty self-documenting 12 | ``` 13 | 14 | # Camera Modes 15 | ## Over The Shoulder 16 | 17 | ![image](https://user-images.githubusercontent.com/73378249/177252958-51185a06-b85f-4f87-88d4-6ef13d829880.png) 18 | 19 | ## Top Down Camera 20 | ![image](https://user-images.githubusercontent.com/73378249/175833398-4114805f-549b-462c-922a-90277a2c42b6.png) 21 | 22 | ## Isometric Camera 23 | ![image](https://user-images.githubusercontent.com/73378249/175834608-4d90a4ce-fae4-4bba-a0df-e483e99d5c65.png) 24 | 25 | ## Side-Scrolling Camera 26 | 27 | ![image](https://user-images.githubusercontent.com/73378249/175834668-7f013fdd-ad43-406c-85fa-8853b98ccffc.png) 28 | 29 | ## Follow-Mouse 30 | ![image](https://user-images.githubusercontent.com/73378249/175834787-5ec8ec55-b6e1-47f0-b332-d0245a6944d9.png) 31 | 32 | 33 | # Design 34 | Purely math-based cameras designed from scratch. Intended to be lightweight and flexible for future-proofing. All the math I wrote is carefully optimized in consideration of modern architectures and in consideration to lower-end architectures like ARM. For those who crave all the slightest optimizations possible, I made a separate CameraModes module (FastCameraModes) but I leave my cautions as readability is sacrificed considerably. 35 | 36 | The challenge posed for deriving and writing a fast camera: 37 | 38 | * GC footprint: Luau's GC (garbage collector) is notorious when it comes to cleaning up userdata creation or even table creations that may result in GC Assist instructions which will halt Luau scripts for this extra work needing to be done by the GC. 39 | * Consideration of Luau->C/C++ bridge invocations: Biggest bottleneck Roblox games face. It is extremely expensive to bridge data between C++ and Luau often making calculations in Luau much faster than they could be in C++. 40 | * Optimizing Expensive Calculations: Considering cases where components could cancel out other components and finding shortcut calculations. 41 | * Numerical Stability: Unfortunately, calculations in C++ have more precision than Luau but this is negligible, however, it is important to take into consideration of errors that may stem from this thus designing math to be more numerically robust is something that should always be noted. 42 | * Consideration of current compiler optimizations: It is important to make sure for example that upvalues should stay constant, otherwise, the compiler won't be able to cache function closures which is a reasonable performance loss. If we can't cache closures and avoid this, then it is best to cache function constants as upvalues otherwise. 43 | * Further target hardware considerations such as cache locality and branch prediction: Although from the perspective of Luau, the impact is minimal however we can still considerably influence this. When possible, division is avoided and multiplication is favored by multiplying ratios (1/x) instead as this is still faster on ARM but doesn't hurt other architectures however bypass delays are also taken into consideration as inverse division leads to interleaving integer and floating operations. 44 | -------------------------------------------------------------------------------- /src/CameraModes.lua: -------------------------------------------------------------------------------- 1 | --[[ -------------------------------------------------------------------- 2 | 3 | -- 4thAxis 4 | -- 6/20/22 5 | 6 | MIT License 7 | 8 | Copyright (c) 2022 4thAxis 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | ]] -------------------------------------------------------------------- 28 | 29 | 30 | local Module = {} 31 | 32 | -------------------------------------------------------------------- 33 | --------------------------- Imports ----------------------------- 34 | -------------------------------------------------------------------- 35 | 36 | local Configs = require(script.Parent:WaitForChild("Configurations")) 37 | 38 | -------------------------------------------------------------------- 39 | -------------------------- Services ------------------------------ 40 | -------------------------------------------------------------------- 41 | 42 | local RunService = game:GetService("RunService") 43 | local UserInputService = game:GetService("UserInputService") 44 | local TweenService = game:GetService("TweenService") 45 | local Players = game:GetService("Players") 46 | 47 | -------------------------------------------------------------------- 48 | ------------------------- Constants ------------------------------ 49 | -------------------------------------------------------------------- 50 | 51 | local DownVector = Vector3.new(0,-1,0) 52 | Module.Epsilon = 1e-5 53 | Module.CameraAngleX = 0 54 | Module.CameraAngleY = 0 55 | 56 | local InvRoot2 = 1/math.sqrt(2) 57 | local Right = Vector3.new(1, 0, 0) 58 | local Top = Vector3.new(0, 1, 0) 59 | local Back = Vector3.new(0, 0, 1) 60 | 61 | -------------------------------------------------------------------- 62 | 63 | local Player = Players.LocalPlayer 64 | local Mouse = Player:GetMouse() 65 | 66 | local Camera = workspace.CurrentCamera 67 | local ScreenSize = Camera.ViewportSize 68 | local ScreenSizeX, ScreenSizeY = ScreenSize.X, ScreenSize.Y 69 | local PixelCoordinateRatioX, PixelCoordinateRatioY = 1/ScreenSizeX, 1/ScreenSizeY 70 | 71 | local Character = Player.Character or Player.CharacterAdded:Wait() 72 | local Head = Character:WaitForChild("Head") 73 | local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart") 74 | local Torso = Character:WaitForChild("UpperTorso") 75 | local Root = Character:WaitForChild("HumanoidRootPart") 76 | local Neck = Head:WaitForChild("Neck") 77 | local Waist = Torso:WaitForChild("Waist") 78 | local NeckOriginC0 = Neck.C0 79 | local WaistOriginC0 = Waist.C0 80 | 81 | Neck.MaxVelocity = 1/3 82 | 83 | -------------------------------------------------------------------- 84 | -------------------------- Privates ------------------------------ 85 | -------------------------------------------------------------------- 86 | 87 | local function DisableRobloxCamera() 88 | if Camera.CameraType ~= Enum.CameraType.Scriptable then 89 | Camera.CameraType = Enum.CameraType.Scriptable 90 | end 91 | end 92 | 93 | 94 | local function GetRotationXY(X, Y) 95 | X = X or Module.CameraAngleX 96 | Y = Y or Module.CameraAngleY 97 | 98 | local Cosy, Siny = math.cos(X), math.sin(X) 99 | local Cosx, Sinx = math.cos(Y), math.sin(Y) 100 | return CFrame.new( 101 | 0, 0, 0, 102 | Cosy, Siny*Sinx, Siny*Cosx, 103 | 0, Cosx, -Sinx, 104 | -Siny, Cosy*Sinx, Cosy*Cosx 105 | ) 106 | end 107 | 108 | 109 | local function GetPositionToWorldByOffset(OriginCF, XOffset, YOffset, ZOffset) 110 | -- Perserve rotational matrix, only transform position to world instead rather than naively transforming origin cframe to world space 111 | XOffset = XOffset or Configs.CamLockOffset.X 112 | YOffset = YOffset or Configs.CamLockOffset.Y 113 | ZOffset = ZOffset or Configs.CamLockOffset.Z 114 | 115 | local X, Y, Z, M11, M12, M13, M21, M22, M23, M31, M32, M33 = OriginCF:GetComponents() 116 | return Vector3.new ( 117 | M11*XOffset+M12*YOffset+M13*ZOffset+X, 118 | M21*XOffset+M22*YOffset+M23*ZOffset+Y, 119 | M31*XOffset+M32*YOffset+M33*ZOffset+Z 120 | ) 121 | end 122 | 123 | 124 | local function ZBasisToObjectSpace(ZBasis, Offset) 125 | local x, y, z, a11, a12, a13, 126 | a21, a22, a23, 127 | a31, a32, a33 = Offset:GetComponents() 128 | -- Z-basis of rotational matrix, we actually have a singular matrix meaning it is non-invertable e.g ZBasis:Invert()=ZBasis so this is nice short-cut 129 | local _, _, _, _, _, R13, 130 | _, _, R23, 131 | _, _, R33 = ZBasis:GetComponents() 132 | 133 | return x, y, z, 134 | 0, 0, a11*R13+a12*R23+a13*R33, 135 | 0, 0, a21*R13+a22*R23+a23*R33, 136 | 0, 0, a31*R13+a32*R23+a33*R33 137 | end 138 | 139 | 140 | local function MatrixFromAxisAngles(PivotAxis, Theta, Delta, Alpha) 141 | Theta = Theta*Alpha 142 | PivotAxis = PivotAxis.Unit 143 | 144 | local SinTheta, CosTheta = math.sin(Theta), math.cos(Theta) 145 | local RightDotPivot = PivotAxis.X -- Right:Dot(Pivot) 146 | local PivotCrossRight = Vector3.new(0, PivotAxis.Z, -PivotAxis.Y) -- Pivot:Cross(Right) 147 | local TopDotPivot = PivotAxis.Y -- Top:Dot(Pivot) 148 | local PivotCrossTop = Vector3.new(-PivotAxis.Z, 0, PivotAxis.X) -- PivotCrossTop 149 | local BackDotPivot = PivotAxis.Z -- Back:Dot(Pivot) 150 | local PivotCrossBack = Vector3.new(PivotAxis.Y, -PivotAxis.X, 0) 151 | 152 | -- Can considerably optimize this but got lazy-- would look like this: https://gyazo.com/dceaf793453bc48b1731526c3a70c6d8 in case anyone dares to do so... 153 | 154 | local Right = Right*Theta+RightDotPivot*PivotAxis*(1-Theta)+PivotCrossRight*Theta 155 | local Top = Top*Theta+TopDotPivot*PivotAxis*(1-Theta)+PivotCrossRight*Theta 156 | local Back = Back*Theta+BackDotPivot*PivotAxis*(1-Theta)+PivotCrossRight*Theta 157 | 158 | return CFrame.new( 159 | 0, 0, 0, 160 | Right.X, Top.X, Back.X, 161 | Right.Y, Top.Y, Back.Y, 162 | Right.Z, Top.Z, Back.Z 163 | ) + Delta*Alpha 164 | end 165 | 166 | 167 | local function SlerpInvertedRotation(Motor, PivotAxis, Theta, Delta, Alpha, M11, M12, M13, M21, M22, M23, M31, M32, M33) 168 | if M11 > M22 and M11 > M33 then 169 | if M11 < 1e-4 then 170 | PivotAxis = Vector3.new(0, InvRoot2, InvRoot2) 171 | else 172 | local x = math.sqrt(M11) 173 | local invx = 1/x 174 | M21 = (M21 + M12)/4 175 | M31 = (M31 + M13)/4 176 | PivotAxis = Vector3.new(x, M21*invx, M31*invx) 177 | end 178 | return Motor * MatrixFromAxisAngles(PivotAxis, Theta, Delta, Alpha) 179 | 180 | elseif M22 > M33 then 181 | if M22 < 1e-4 then 182 | PivotAxis = Vector3.new(InvRoot2, 0, InvRoot2) 183 | else 184 | local y = math.sqrt(M22) 185 | local invy = 1/y 186 | M21 = (M21 + M12)/4 187 | M32 = (M32 + M23)/4 188 | PivotAxis = Vector3.new(M21*invy, y, M32*invy) 189 | end 190 | return Motor * MatrixFromAxisAngles(PivotAxis, Theta, Delta, Alpha) 191 | 192 | else 193 | if M33 < 1e-4 then 194 | PivotAxis = Vector3.new(InvRoot2, InvRoot2, 0) 195 | else 196 | local z = math.sqrt(M33) 197 | local invz = 1/z 198 | M31 = (M31 + M13)/4 199 | M32 = (M33 + M23)/4 200 | PivotAxis = Vector3.new(M31*invz, M32*invz, z) 201 | end 202 | 203 | return Motor * MatrixFromAxisAngles(PivotAxis, Theta, Delta, Alpha) 204 | end 205 | end 206 | 207 | 208 | local function LerpFromZBasisRotationMatrix(Motor, ZBasis, Alpha) 209 | -- SLERP while avoiding quarternions, specifically written. Not sure if a generalized custom lerp would come close to gapping CFrame:Lerp() 210 | Alpha = Alpha or 0.5 211 | 212 | local _, _, _, M11, M12, M13, 213 | M21, M22, M23, 214 | M31, M32, M33 = ZBasisToObjectSpace(ZBasis, Motor) 215 | 216 | local CoTheta = (M11 + M22 + M33 - 1)*0.5 217 | local Delta = (ZBasis.Position-Motor.Position) 218 | if CoTheta == 0 then return Motor + Delta*Alpha end -- Rotations are aligned, interpolate positions 219 | 220 | local PivotAxis = Vector3.new(M32-M23, M13-M31, M21-M12) -- Rotation Axis 221 | if CoTheta >= 0.999 then -- Close but not perfectly aligned rotations, best to just LERP... 222 | local a = 1 - Alpha 223 | local _, _, _, a11, a12, a13, 224 | a21, a22, a23, 225 | a31, a32, a33 = Motor:GetComponents() 226 | local _, _, _, _, _, R13, 227 | _, _, R23, 228 | _, _, R33 = ZBasis:GetComponents() 229 | 230 | return CFrame.new( 231 | 0, 0, 0, 232 | a11*a, a12*a, a13*a+R13*Alpha, 233 | a21*a, a22*a, a23*a+R23*Alpha, 234 | a31*a, a32*a, a33*a+R33*Alpha 235 | ) + (Motor.Position + Delta*Alpha) 236 | 237 | elseif CoTheta <= -0.9999 then -- exactly opposite rotations, probably not common but exists for numerical scalability practice 238 | M11, M22, M33 = (M11+1)/2, (M22+1)/2, (M33+1)/2 -- 0.5*Mn+0.5 sounds nice but it really isn't 239 | return SlerpInvertedRotation(Motor, PivotAxis, math.pi, Delta, Alpha, 240 | M11, M12, M13, 241 | M21, M22, M23, 242 | M31, M32, M33 243 | ) 244 | else -- Normal case 245 | return Motor * MatrixFromAxisAngles(PivotAxis, math.acos(CoTheta), Delta, Alpha) 246 | end 247 | end 248 | 249 | 250 | local function FastCFLerpCase(MotorC0, YOffset, Alpha, R11, R12, R13, R21, R22, R23, R31, R32, R33, OrthogonalMatrix) 251 | local ax, ay, az, a11, a12, a13, a21, a22, a23, a31, a32, a33 = MotorC0:GetComponents() 252 | local Determinant = a11*a22*a33+a12*a23*a31+a13*a21*a32-a11*a23*a32-a12*a21*a33-a13*a22*a31 253 | if Determinant~=0 then return false end -- if det is 0, we don't have to calculute for matrix inverse; shortcut lerp. Otherwise, Roblox here probably benefits from SIMD hardware optimizations which typically use elimination methods. Technically orthogonal matrices shouldn't have det=0 but who said this can't be just used with orthongal matrices ;) 254 | -- convert goal to start's object space with inversion omitted 255 | local x, y, z = ax,ay, az 256 | R11, R12, R13 = 0, 0, a11*R13+a12*R23+a13*R33 257 | R21, R22, R23 = 0, 0, a21*R13+a22*R23+a23*R33 258 | R31, R32, R33 = 0, 0, a31*R13+a32*R23+a33*R33 259 | 260 | if (R33 > 0) then -- (R11+R22+R33)>0 (our trace) 261 | local Pos = MotorC0.Position + (Vector3.new(x, y+YOffset, z) - MotorC0.Position) * Alpha*0.5 262 | local Theta = math.acos(R23*(0.5/(math.sqrt(1+R33)))) -- possible to cancel out square root 263 | if Theta == 0 then -- theta~=0 is too expensive to handle because we need c0* matrix from axis angles for rotation and additional stuff if we go with this approach... 264 | return CFrame.new ( 265 | Pos.X, Pos.Y, Pos.Z, 266 | R11, R12, R13, 267 | R21, R22, R23, 268 | R31, R32, R33 269 | ) 270 | end 271 | end 272 | 273 | return false 274 | end 275 | 276 | 277 | local function SlerpXY(Origin, GoalPos, GoalLook, Alpha) 278 | Alpha = math.clamp(Alpha or 0.5, 0, 1) 279 | local Theta = math.acos(Origin.lookVector:Dot(GoalLook)) -- LookVector of goal cframe 280 | if Theta < 0.01 then 281 | return Origin 282 | else 283 | local Position = Origin.Position:Lerp(GoalPos, Alpha) 284 | local InvSin = 1/math.sin(Theta) 285 | local Rotation = math.sin((1-Alpha)*Theta)*InvSin*Origin.LookVector + math.sin(Alpha*Theta)*InvSin*GoalLook 286 | return CFrame.lookAt( 287 | Position, 288 | Position + Rotation 289 | ) 290 | end 291 | end 292 | 293 | 294 | local function TransformMotor(Motor, MotorOriginC0, x, y, YOffset, Alpha) 295 | YOffset = YOffset or 0 296 | Alpha = Alpha or 0.5 297 | local MotorC0 = Motor.C0 298 | -- rotation matrix -- 299 | local cosx, sinx = math.cos(x), math.sin(x) 300 | local cosy, siny = math.cos(y), math.sin(y) 301 | local R11, R12, R13 = 0, 0, siny 302 | local R21, R22, R23 = 0, 0, -cosy*sinx 303 | local R31, R32, R33 = 0, 0, cosx*cosy 304 | -- transformation matrix (OriginC0*rotation) -- 305 | local ax, ay, az, a11, a12, a13, a21, a22, a23, a31, a32, a33 = MotorOriginC0:GetComponents() 306 | local x, y, z = ax, ay, az 307 | R11, R12, R13 = 0, 0, a11*R13+a12*R23+a13*R33 308 | R21, R22, R23 = 0, 0, a21*R13+a22*R23+a23*R33 309 | R31, R32, R33 = 0, 0, a31*R13+a32*R23+a33*R33 310 | 311 | local FromFastLerp = FastCFLerpCase(MotorC0, YOffset, Alpha, R11, R12, R13, R21, R22, R23, R31, R32, R33, true) -- calculute for orthongonal matrix -> only rotational matrix 312 | return FromFastLerp or SlerpXY ( 313 | Motor.C0, 314 | Vector3.new(x, y, z), 315 | Vector3.new(-R13, -R23, -R33), 316 | Alpha 317 | ) 318 | end 319 | 320 | 321 | local function GetViewMatrix(Eye, Focus) 322 | -- Faster alternative to cframe.lookat for our case since we are more commonly prone to special cases such as: when focus is facing up/down or if focus and eye are colinear vectors 323 | local XAxis = Focus-Eye -- Lookvector 324 | if (XAxis:Dot(XAxis) <= Module.Epsilon) then 325 | return CFrame.new(Eye.X, Eye.Y, Eye.Z, 1, 0, 0, 0, 1, 0, 0, 0, 1) 326 | end 327 | XAxis = XAxis.Unit 328 | local Xx, Xy, Xz = XAxis.X, XAxis.Y, XAxis.Z 329 | local RNorm = (((Xz*Xz)+(Xx*Xx))) -- R:Dot(R), our right vector 330 | if RNorm <= Module.Epsilon and math.abs(XAxis.Y) > 0 then 331 | return CFrame.fromMatrix(Eye, -math.sign(XAxis.Y)*Vector3.zAxis, Vector3.xAxis) 332 | end 333 | RNorm = 1/(RNorm^0.5) -- take the root of our squared norm and inverse division 334 | local Rx, Rz = -(Xz*RNorm), (Xx*RNorm) -- cross y-axis with right and normalize 335 | local Ux, Uy, Uz = -Rz*(Rz*Xx-Rx*Xz), -(Rz*Rz)*Xy-(Rx*Rx)*Xy, Rx*(Rz*Xx-Rx*Xz) -- cross right and up and normalize. 336 | local UNorm = 1/((Ux*Ux)+(Uy*Uy)+(Uz*Uz))^0.5 -- inverse division and multiply this ratio rather than dividing each component 337 | return CFrame.new( 338 | Eye.X,Eye.Y,Eye.Z, 339 | Rx, -Xy*Rz, Ux*UNorm, 340 | 0, (Rz*Xx)-Rx*Xz, Uy*UNorm, 341 | Rz, Xy*Rx, Uz*UNorm 342 | ) 343 | end 344 | 345 | -------------------------------------------------------------------- 346 | ------------------------- Functions ------------------------------ 347 | -------------------------------------------------------------------- 348 | 349 | Module.OverTheShoulder = function(_, CameraAngleX, CameraAngleY, Lerp) 350 | Module.CameraAngleX = CameraAngleX or Module.CameraAngleX 351 | Module.CameraAngleY = CameraAngleY or Module.CameraAngleY 352 | DisableRobloxCamera() 353 | 354 | local Origin = CFrame.new((HumanoidRootPart.CFrame.Position)) * GetRotationXY(math.rad(Module.CameraAngleX), math.rad(Module.CameraAngleY)) 355 | local Eye = GetPositionToWorldByOffset(Origin) 356 | local Focus = GetPositionToWorldByOffset(Origin, Configs.CamLockOffset.X, Configs.CamLockOffset.Y, -10000) 357 | 358 | Camera.CFrame = GetViewMatrix(Eye, Focus) 359 | end 360 | 361 | 362 | Module.IsometricCamera = function(_, CameraDepth, HeightOffset, FOV) 363 | CameraDepth = CameraDepth or Configs.IsometricCameraDepth 364 | HeightOffset = HeightOffset or Configs.IsometricHeightOffset 365 | Camera.FieldOfView = FOV or Configs.IsometricFieldOfView 366 | DisableRobloxCamera() 367 | 368 | local Root = HumanoidRootPart.Position + Vector3.new(0, HeightOffset, 0) 369 | local Eye = Root + Vector3.new(CameraDepth, CameraDepth, CameraDepth) 370 | Camera.CFrame = GetViewMatrix(Eye, Root) 371 | end 372 | 373 | 374 | Module.SideScrollingCamera = function(_, CameraDepth, HeightOffset, FOV) 375 | CameraDepth = CameraDepth or Configs.SideCameraDepth 376 | HeightOffset = HeightOffset or Configs.SideHeightOffset 377 | Camera.FieldOfView = FOV or Configs.SideFieldOfView 378 | DisableRobloxCamera() 379 | 380 | local Focus = HumanoidRootPart.Position + Vector3.new(0, HeightOffset, 0) 381 | local Eye = Vector3.new(Focus.X, Focus.Y, CameraDepth) 382 | Camera.CFrame = GetViewMatrix(Eye, Focus) 383 | end 384 | 385 | 386 | Module.TopDownCamera = function(_, FaceMouse, MouseSensitivity, Offset, Direction, Distance) 387 | FaceMouse = FaceMouse or Configs.TopDownFaceMouse 388 | MouseSensitivity = MouseSensitivity or Configs.TopDownMouseSensitivity 389 | Distance = Distance or Configs.TopDownDistance 390 | Direction = Direction or DownVector 391 | Offset = Offset or Configs.TopDownOffset 392 | DisableRobloxCamera() 393 | 394 | local M = UserInputService:GetMouseLocation() 395 | local Axis = Vector3.new(-((M.y-ScreenSizeY*0.5)*PixelCoordinateRatioY),0,((M.x-ScreenSizeX*0.5)*PixelCoordinateRatioX)) 396 | 397 | local Eye = (Distance + (Root.Position+Offset)) + Axis * MouseSensitivity 398 | local Focus = Eye + Direction 399 | Camera.CFrame = GetViewMatrix(Eye, Focus) 400 | 401 | if FaceMouse then 402 | local Forward = (Root.Position - Mouse.Hit.Position).Unit 403 | local Right = Vector3.new(-Forward.Z, 0, Forward.X) -- Forward:Cross(YAxis) 404 | Root.CFrame = CFrame.fromMatrix(Root.Position, -Right, Vector3.yAxis) 405 | end 406 | end 407 | 408 | 409 | Module.HeadFollowCamera = function(_, Alpha) 410 | if not (Camera.CameraSubject:IsDescendantOf(Character) or Camera.CameraSubject:IsDescendantOf(Player)) then return end 411 | Alpha = Alpha or Configs.HeadFollowAlpha 412 | local ZColumn = -(Camera.CFrame.LookVector) 413 | ZColumn = ZColumn - Vector3.new(0, ZColumn.Y, 0) 414 | local XColumn = Vector3.yAxis:Cross(ZColumn) 415 | 416 | if Torso.CFrame:PointToObjectSpace(Head.Position+ZColumn).Z > 0 then 417 | Neck.C0 = Neck.C0:Lerp(NeckOriginC0 * CFrame.fromMatrix(Vector3.zero, XColumn, Vector3.yAxis, ZColumn), Alpha) 418 | Waist.C0 = Waist.C0:Lerp(WaistOriginC0 * CFrame.fromMatrix(Vector3.zero, XColumn*0.5, Vector3.yAxis, ZColumn), Alpha) 419 | end 420 | end 421 | 422 | 423 | Module.FaceCharacterToMouse = function(_, Alpha, GoalCF) 424 | GoalCF = GoalCF or GetViewMatrix(Root.Position, Vector3.new(Mouse.Hit.Position.X, Root.Position.Y, Mouse.Hit.Position.Z)) 425 | Root.CFrame = Root.CFrame:Lerp(GoalCF, Alpha or Configs.FaceCharacterAlpha) 426 | end 427 | 428 | 429 | Module.FollowMouse = function(Dt, Alpha, XOffset, YOffset) 430 | Alpha = Alpha or Configs.MouseAlpha 431 | XOffset = XOffset or Configs.MouseXOffset 432 | YOffset = YOffset or Configs.MouseYOffset 433 | DisableRobloxCamera() 434 | 435 | local Easing = Configs.MouseCameraEasingStyle and TweenService:GetValue(Configs.MouseCameraSmoothness, Configs.MouseCameraEasingStyle, Configs.MouseCameraEasingDirection or Enum.EasingDirection.Out) 436 | local Goal = Head.CFrame * CFrame.new(0, Configs.AspectRatio.Y, Configs.AspectRatio.X) * GetRotationXY(XOffset, math.rad(YOffset)) 437 | if Easing then 438 | Camera.CFrame = Camera.CFrame:Lerp(Goal, Easing) 439 | else 440 | local a = Configs.MouseCameraSmoothness-1 441 | Camera.CFrame = Camera.CFrame:Lerp(Goal, (a*a*a*a*a)+1) 442 | end 443 | 444 | local P = Mouse.Hit.Position 445 | local Distance = (Head.CFrame.Position - P).Magnitude 446 | local Difference = Head.CFrame.Y-P.Y 447 | local X, Y = -math.atan(Difference/Distance)*0.5, (((Head.CFrame.Position-P).Unit):Cross(Torso.CFrame.LookVector)).Y 448 | 449 | Neck.C0 = TransformMotor(Neck, NeckOriginC0, X, Y, 1, 0.5) 450 | Waist.C0 = TransformMotor(Waist, WaistOriginC0, X, Y*0.5, 0, 0.5) 451 | end 452 | 453 | 454 | 455 | return Module 456 | -------------------------------------------------------------------------------- /src/CameraSystem.lua: -------------------------------------------------------------------------------- 1 | --[[ -------------------------------------------------------------------- 2 | 3 | -- 4thAxis 4 | -- 6/20/22 5 | 6 | MIT License 7 | 8 | Copyright (c) 2022 4thAxis 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | ]] -------------------------------------------------------------------- 29 | 30 | 31 | 32 | local Module = {} 33 | Module.Connections = {} 34 | 35 | -------------------------------------------------------------------- 36 | --------------------------- Imports ----------------------------- 37 | -------------------------------------------------------------------- 38 | 39 | local CameraModes = require(script:WaitForChild("CameraModes")) 40 | 41 | -------------------------------------------------------------------- 42 | -------------------------- Services ------------------------------ 43 | -------------------------------------------------------------------- 44 | 45 | local RunService = game:GetService("RunService") 46 | local UserInputService = game:GetService("UserInputService") 47 | local ContextActionService = game:GetService("ContextActionService") 48 | local Players = game:GetService("Players") 49 | 50 | -------------------------------------------------------------------- 51 | -------------------------- Privates ------------------------------ 52 | -------------------------------------------------------------------- 53 | 54 | local function LockCenter(Input) 55 | if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then 56 | UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter 57 | end 58 | end 59 | 60 | 61 | local function MouseMovementTrack(_, Input, Object) 62 | if Input == Enum.UserInputState.Change then 63 | CameraModes.OverTheShoulder(nil, CameraModes.CameraAngleX-Object.Delta.X, math.clamp(CameraModes.CameraAngleY-Object.Delta.Y*0.4, -75, 75)) 64 | end 65 | end 66 | 67 | 68 | -------------------------------------------------------------------- 69 | ------------------------- Functions ------------------------------ 70 | -------------------------------------------------------------------- 71 | 72 | --------------------------- 73 | ---- Shift Lock ---- 74 | --------------------------- 75 | 76 | function Module.EnableShiftLockCamera() 77 | Module.Connections.LockCenter = UserInputService.InputBegan:Connect(LockCenter) 78 | ContextActionService:BindAction("MouseMovementTrack", MouseMovementTrack, false, Enum.UserInputType.MouseMovement, Enum.UserInputType.Touch) 79 | RunService:BindToRenderStep("ShiftLock", Enum.RenderPriority.Camera.Value, CameraModes.OverTheShoulder) 80 | end 81 | 82 | function Module.DisableShiftLockCamera() 83 | if not Module.Connections.LockCenter and not Module.Connections.MouseMovementTrack then return end 84 | 85 | Module.Connections.LockCenter:Disconnect() 86 | ContextActionService:UnbindAction("MouseMovementTrack") 87 | RunService:UnbindFromRenderStep("ShiftLock") 88 | end 89 | 90 | --------------------------- 91 | ---- Isometric Camera ---- 92 | --------------------------- 93 | 94 | function Module.EnableIsometricCamera() 95 | RunService:BindToRenderStep("IsometricCamera", Enum.RenderPriority.Camera.Value, CameraModes.IsometricCamera) 96 | 97 | end 98 | 99 | function Module.DisableIsometricCamera() 100 | RunService:UnbindFromRenderStep("IsometricCamera") 101 | end 102 | 103 | --------------------------- 104 | ---- Top Down Camera ---- 105 | --------------------------- 106 | 107 | function Module.EnableTopDownCamera() 108 | RunService:BindToRenderStep("TopDown", Enum.RenderPriority.Camera.Value, CameraModes.TopDownCamera) 109 | 110 | end 111 | 112 | function Module.DisableTopDownCamera() 113 | RunService:UnbindFromRenderStep("TopDown") 114 | 115 | end 116 | 117 | --------------------------- 118 | ---- SideScrollCamera ---- 119 | --------------------------- 120 | 121 | function Module.EnableSideScrollingCamera() 122 | RunService:BindToRenderStep("SideScroll", Enum.RenderPriority.Camera.Value, CameraModes.SideScrollingCamera) 123 | end 124 | 125 | function Module.DisableSideScrollingCamera() 126 | RunService:UnbindFromRenderStep("SideScroll") 127 | 128 | end 129 | 130 | --------------------------- 131 | ---- Follow Mouse ---- 132 | --------------------------- 133 | 134 | function Module.FollowMouse() 135 | Module.DisableShiftLockCamera() 136 | RunService:BindToRenderStep("FollowMouse", Enum.RenderPriority.Camera.Value, CameraModes.FollowMouse) 137 | end 138 | 139 | function Module.StopFollowingMouse() 140 | RunService:UnbindFromRenderStep("FollowMouse") 141 | end 142 | 143 | --------------------------- 144 | ---- Face Mouse ---- 145 | --------------------------- 146 | 147 | function Module.FaceCharacterToMouse() 148 | RunService:BindToRenderStep("FaceCharacterToMouse", Enum.RenderPriority.Character.Value, CameraModes.FaceCharacterToMouse) 149 | end 150 | 151 | function Module.StopFacingMouse() 152 | RunService:UnbindFromRenderStep("FaceCharacterToMouse") 153 | end 154 | 155 | --------------------------- 156 | ---- Head Follow Mouse ---- 157 | --- (Experimental) ---- 158 | --------------------------- 159 | 160 | function Module.HeadFollowCamera() 161 | RunService:BindToRenderStep("HeadFollowCamera", Enum.RenderPriority.Character.Value, CameraModes.HeadFollowCamera) 162 | end 163 | 164 | function Module.StopHeadFollowCamera() 165 | RunService:UnbindFromRenderStep("HeadFollowCamera") 166 | end 167 | 168 | 169 | 170 | return Module 171 | -------------------------------------------------------------------------------- /src/Configs.Lua: -------------------------------------------------------------------------------- 1 | local Module = {} 2 | 3 | -------------------------------------------------------------------- 4 | ------------------ Over The Shoulder Camera ---------------------- 5 | -------------------------------------------------------------------- 6 | 7 | Module.CamLockOffset = Vector3.new(1, 3, 9.5) 8 | 9 | -------------------------------------------------------------------- 10 | ----------------------- Isometric Camera ------------------------- 11 | -------------------------------------------------------------------- 12 | 13 | Module.IsometricCameraDepth = 64 14 | Module.IsometricHeightOffset = 2 15 | Module.IsometricFieldOfView = 20 16 | 17 | -------------------------------------------------------------------- 18 | --------------------- Side Scrolling Camera ---------------------- 19 | -------------------------------------------------------------------- 20 | 21 | Module.SideCameraDepth = 64 22 | Module.SideHeightOffset = 2 23 | Module.SideFieldOfView = 20 24 | 25 | -------------------------------------------------------------------- 26 | ----------------------- Top Down Camera -------------------------- 27 | -------------------------------------------------------------------- 28 | 29 | Module.TopDownMouseSensitivity = 20 30 | Module.TopDownDistance = Vector3.new(0,25,0) 31 | Module.TopDownDirection = Vector3.new(0, -1, 0) 32 | Module.TopDownOffset = Vector3.new(0,0,3) 33 | Module.TopDownFaceMouse = true 34 | 35 | -------------------------------------------------------------------- 36 | ---------------------- Head Follow Camera ------------------------ 37 | -------------------------------------------------------------------- 38 | 39 | Module.HeadFollowAlpha = 0.5 40 | 41 | -------------------------------------------------------------------- 42 | -------------------- Face Character To Mouse --------------------- 43 | -------------------------------------------------------------------- 44 | 45 | Module.FaceCharacterAlpha = 0.5 46 | 47 | -------------------------------------------------------------------- 48 | ----------------------- Follow Mouse Camera ----------------------- 49 | -------------------------------------------------------------------- 50 | 51 | Module.MouseCameraEasingStyle = nil -- If left nil, this will default to a fast quint fallback 52 | Module.MouseCameraEasingDirection = nil -- If left nil, this will default to enum.easing.out direction 53 | Module.MouseCameraSmoothness = 0.03 54 | Module.AspectRatio = Vector2int16.new(15, 5) -- X, Y 55 | 56 | -------------------------------------------------------------------- 57 | 58 | 59 | return Module 60 | -------------------------------------------------------------------------------- /src/FasterCameraModes.lua: -------------------------------------------------------------------------------- 1 | -- This module is a faster but slightly less readable version. 2 | -- This module also contains the exact same functionality, there 3 | -- is no difference apart from speed-ups. 4 | 5 | --[[ -------------------------------------------------------------------- 6 | 7 | -- 4thAxis 8 | -- 6/20/22 9 | 10 | MIT License 11 | 12 | Copyright (c) 2022 4thAxis 13 | 14 | Permission is hereby granted, free of charge, to any person obtaining a copy 15 | of this software and associated documentation files (the "Software"), to deal 16 | in the Software without restriction, including without limitation the rights 17 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the Software is 19 | furnished to do so, subject to the following conditions: 20 | 21 | The above copyright notice and this permission notice shall be included in all 22 | copies or substantial portions of the Software. 23 | 24 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 30 | SOFTWARE. 31 | ]] -------------------------------------------------------------------- 32 | 33 | 34 | local Module = {} 35 | 36 | -------------------------------------------------------------------- 37 | --------------------------- Imports ----------------------------- 38 | -------------------------------------------------------------------- 39 | 40 | local Configs = require(script.Parent:WaitForChild("Configurations")) 41 | 42 | -------------------------------------------------------------------- 43 | -------------------------- Services ------------------------------ 44 | -------------------------------------------------------------------- 45 | 46 | local RunService = game:GetService("RunService") 47 | local TweenService = game:GetService("TweenService") 48 | local UserInputService = game:GetService("UserInputService") 49 | local Players = game:GetService("Players") 50 | 51 | -------------------------------------------------------------------- 52 | ------------------------- Constants ------------------------------ 53 | -------------------------------------------------------------------- 54 | 55 | Module.Epsilon = 1e-5 56 | Module.CameraAngleX = 0 57 | Module.CameraAngleY = 0 58 | 59 | local DownVector = Vector3.new(0,-1,0) 60 | local Rad2 = math.rad(2) 61 | local Cos270 = math.cos(270) 62 | local Sin270 = math.sin(270) 63 | 64 | -------------------------------------------------------------------- 65 | 66 | local Player = game.Players.LocalPlayer 67 | local Mouse = Player:GetMouse() 68 | 69 | local Camera = workspace.CurrentCamera 70 | local ScreenSize = Camera.ViewportSize 71 | local ScreenSizeX, ScreenSizeY = ScreenSize.X, ScreenSize.Y 72 | local PixelCoordinateRatioX, PixelCoordinateRatioY = 1/ScreenSizeX, 1/ScreenSizeY 73 | 74 | local Character = Player.Character or Player.CharacterAdded:Wait() 75 | local Head = Character:WaitForChild("Head") 76 | local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart") 77 | local Torso = Character:WaitForChild("UpperTorso") 78 | local Root = Character:WaitForChild("HumanoidRootPart") 79 | local Neck = Head:WaitForChild("Neck") 80 | local Waist = Torso:WaitForChild("Waist") 81 | local NeckOriginC0 = Neck.C0 82 | local WaistOriginC0 = Waist.C0 83 | 84 | Neck.MaxVelocity = 1/3 85 | 86 | -------------------------------------------------------------------- 87 | -------------------------- Privates ------------------------------ 88 | -------------------------------------------------------------------- 89 | 90 | local function DisableRobloxCamera() 91 | if Camera.CameraType ~= Enum.CameraType.Scriptable then 92 | Camera.CameraType = Enum.CameraType.Scriptable 93 | end 94 | end 95 | 96 | -------------------------------------------------------------------- 97 | ------------------------- Functions ------------------------------ 98 | -------------------------------------------------------------------- 99 | 100 | Module.OverTheShoulder = function(_, CameraAngleX, CameraAngleY, Lerp) 101 | Module.CameraAngleX = CameraAngleX or Module.CameraAngleX 102 | Module.CameraAngleY = CameraAngleY or Module.CameraAngleY 103 | DisableRobloxCamera() 104 | 105 | local RadX, RadY = math.rad(Module.CameraAngleX), math.rad(Module.CameraAngleY) 106 | local Cosy, Siny = math.cos(RadX), math.sin(RadX) 107 | local Cosx, Sinx = math.cos(RadY), math.sin(RadY) 108 | local Origin = CFrame.new((HumanoidRootPart.CFrame.Position)) * CFrame.new( 109 | 0, 0, 0, 110 | Cosy, Siny*Sinx, Siny*Cosx, 111 | 0, Cosx, -Sinx, 112 | -Siny, Cosy*Sinx, Cosy*Cosx 113 | ) 114 | 115 | local XOffset, YOffset, ZOffset= Configs.CamLockOffset.X, Configs.CamLockOffset.Y, Configs.CamLockOffset.Z 116 | local X, Y, Z, M11, M12, M13, M21, M22, M23, M31, M32, M33 = Origin:GetComponents() 117 | 118 | local Eye = Vector3.new (M11*XOffset+M12*YOffset+M13*ZOffset+X,M21*XOffset+M22*YOffset+M23*ZOffset+Y,M31*XOffset+M32*YOffset+M33*ZOffset+Z) 119 | local XAxis = Vector3.new (M11*XOffset+M12*YOffset+M13*-10000+X,M21*XOffset+M22*YOffset+M23*-10000+Y,M31*XOffset+M32*YOffset+M33*-10000+Z)-Eye 120 | 121 | if (XAxis:Dot(XAxis) <= Module.Epsilon) then 122 | Camera.CFrame = CFrame.new(Eye.X, Eye.Y, Eye.Z, 1, 0, 0, 0, 1, 0, 0, 0, 1) 123 | end 124 | XAxis = XAxis.Unit 125 | local Xx, Xy, Xz = XAxis.X, XAxis.Y, XAxis.Z 126 | local RNorm = (((Xz*Xz)+(Xx*Xx))) -- R:Dot(R), our right vector 127 | if RNorm <= Module.Epsilon and math.abs(XAxis.Y) > 0 then 128 | Camera.CFrame = CFrame.fromMatrix(Eye, -math.sign(XAxis.Y)*Vector3.zAxis, Vector3.xAxis) 129 | end 130 | RNorm = 1/(RNorm^0.5) -- take the root of our squared norm and inverse division 131 | local Rx, Rz = -(Xz*RNorm), (Xx*RNorm) -- cross y-axis with right and normalize 132 | local Ux, Uy, Uz = -Rz*(Rz*Xx-Rx*Xz), -(Rz*Rz)*Xy-(Rx*Rx)*Xy, Rx*(Rz*Xx-Rx*Xz) -- cross right and up and normalize. 133 | local UNorm = 1/((Ux*Ux)+(Uy*Uy)+(Uz*Uz))^0.5 -- inverse division and multiply this ratio rather than dividing each component 134 | Camera.CFrame = CFrame.new( 135 | Eye.X,Eye.Y,Eye.Z, 136 | Rx, -Xy*Rz, Ux*UNorm, 137 | 0, (Rz*Xx)-Rx*Xz, Uy*UNorm, 138 | Rz, Xy*Rx, Uz*UNorm 139 | ) 140 | end 141 | 142 | 143 | Module.IsometricCamera = function(_, CameraDepth, HeightOffset, FOV) 144 | CameraDepth = CameraDepth or Configs.IsometricCameraDepth 145 | HeightOffset = HeightOffset or Configs.IsometricHeightOffset 146 | Camera.FieldOfView = FOV or Configs.IsometricFieldOfView 147 | DisableRobloxCamera() 148 | 149 | local Root = HumanoidRootPart.Position + Vector3.new(0, HeightOffset, 0) 150 | local Eye = Root + Vector3.new(CameraDepth, CameraDepth, CameraDepth) 151 | 152 | local XAxis = Root-Eye 153 | if (XAxis:Dot(XAxis) <= Module.Epsilon) then 154 | Camera.CFrame = CFrame.new(Eye.X, Eye.Y, Eye.Z, 1, 0, 0, 0, 1, 0, 0, 0, 1) 155 | end 156 | XAxis = XAxis.Unit 157 | local Xx, Xy, Xz = XAxis.X, XAxis.Y, XAxis.Z 158 | local RNorm = (((Xz*Xz)+(Xx*Xx))) -- R:Dot(R), our right vector 159 | if RNorm <= Module.Epsilon and math.abs(XAxis.Y) > 0 then 160 | Camera.CFrame = CFrame.fromMatrix(Eye, -math.sign(XAxis.Y)*Vector3.zAxis, Vector3.xAxis) 161 | end 162 | RNorm = 1/(RNorm^0.5) -- take the root of our squared norm and inverse division 163 | local Rx, Rz = -(Xz*RNorm), (Xx*RNorm) -- cross y-axis with right and normalize 164 | local Ux, Uy, Uz = -Rz*(Rz*Xx-Rx*Xz), -(Rz*Rz)*Xy-(Rx*Rx)*Xy, Rx*(Rz*Xx-Rx*Xz) -- cross right and up and normalize. 165 | local UNorm = 1/((Ux*Ux)+(Uy*Uy)+(Uz*Uz))^0.5 -- inverse division and multiply this ratio rather than dividing each component 166 | Camera.CFrame = CFrame.new( 167 | Eye.X,Eye.Y,Eye.Z, 168 | Rx, -Xy*Rz, Ux*UNorm, 169 | 0, (Rz*Xx)-Rx*Xz, Uy*UNorm, 170 | Rz, Xy*Rx, Uz*UNorm 171 | ) 172 | end 173 | 174 | 175 | Module.SideScrollingCamera = function(_, CameraDepth, HeightOffset, FOV) 176 | CameraDepth = CameraDepth or Configs.SideCameraDepth 177 | HeightOffset = HeightOffset or Configs.SideHeightOffset 178 | Camera.FieldOfView = FOV or Configs.SideFieldOfView 179 | DisableRobloxCamera() 180 | 181 | local Focus = HumanoidRootPart.Position + Vector3.new(0, HeightOffset, 0) 182 | local Eye = Vector3.new(Focus.X, Focus.Y, CameraDepth) 183 | local XAxis = Focus-Eye 184 | if (XAxis:Dot(XAxis) <= Module.Epsilon) then 185 | Camera.CFrame = CFrame.new(Eye.X, Eye.Y, Eye.Z, 1, 0, 0, 0, 1, 0, 0, 0, 1) 186 | end 187 | XAxis = XAxis.Unit 188 | local Xx, Xy, Xz = XAxis.X, XAxis.Y, XAxis.Z 189 | local RNorm = (((Xz*Xz)+(Xx*Xx))) -- R:Dot(R), our right vector 190 | if RNorm <= Module.Epsilon and math.abs(XAxis.Y) > 0 then 191 | Camera.CFrame = CFrame.fromMatrix(Eye, -math.sign(XAxis.Y)*Vector3.zAxis, Vector3.xAxis) 192 | end 193 | RNorm = 1/(RNorm^0.5) -- take the root of our squared norm and inverse division 194 | local Rx, Rz = -(Xz*RNorm), (Xx*RNorm) -- cross y-axis with right and normalize 195 | local Ux, Uy, Uz = -Rz*(Rz*Xx-Rx*Xz), -(Rz*Rz)*Xy-(Rx*Rx)*Xy, Rx*(Rz*Xx-Rx*Xz) -- cross right and up and normalize. 196 | local UNorm = 1/((Ux*Ux)+(Uy*Uy)+(Uz*Uz))^0.5 -- inverse division and multiply this ratio rather than dividing each component 197 | Camera.CFrame = CFrame.new( 198 | Eye.X,Eye.Y,Eye.Z, 199 | Rx, -Xy*Rz, Ux*UNorm, 200 | 0, (Rz*Xx)-Rx*Xz, Uy*UNorm, 201 | Rz, Xy*Rx, Uz*UNorm 202 | ) 203 | end 204 | 205 | 206 | Module.TopDownCamera = function(_, FaceMouse, MouseSensitivity, Offset, Direction, Distance) 207 | FaceMouse = FaceMouse or Configs.TopDownFaceMouse 208 | MouseSensitivity = MouseSensitivity or Configs.TopDownMouseSensitivity 209 | Distance = Distance or Configs.TopDownDistance 210 | Direction = Direction or DownVector 211 | Offset = Offset or Configs.TopDownOffset 212 | DisableRobloxCamera() 213 | 214 | if FaceMouse then 215 | local Forward = (Root.Position - Mouse.Hit.Position).Unit 216 | local Right = Vector3.new(-Forward.Z, 0, Forward.X) -- Forward:Cross(YAxis) 217 | Root.CFrame = CFrame.fromMatrix(Root.Position, -Right, Vector3.yAxis) 218 | end 219 | 220 | local Mx, My = UserInputService:GetMouseLocation() 221 | local Axis = Vector3.new(-((My-ScreenSizeY*0.5)*PixelCoordinateRatioY),0,((Mx-ScreenSizeX*0.5)*PixelCoordinateRatioX)) 222 | local Eye = (Distance + (Root.Position+Offset)) + Axis * MouseSensitivity 223 | 224 | local XAxis = (Eye + Direction)-Eye 225 | if (XAxis:Dot(XAxis) <= Module.Epsilon) then 226 | Camera.CFrame = CFrame.new(Eye.X, Eye.Y, Eye.Z, 1, 0, 0, 0, 1, 0, 0, 0, 1) 227 | end 228 | XAxis = XAxis.Unit 229 | local Xx, Xy, Xz = XAxis.X, XAxis.Y, XAxis.Z 230 | local RNorm = (((Xz*Xz)+(Xx*Xx))) -- R:Dot(R), our right vector 231 | if RNorm <= Module.Epsilon and math.abs(XAxis.Y) > 0 then 232 | Camera.CFrame = CFrame.fromMatrix(Eye, -math.sign(XAxis.Y)*Vector3.zAxis, Vector3.xAxis) 233 | end 234 | RNorm = 1/(RNorm^0.5) -- take the root of our squared norm and inverse division 235 | local Rx, Rz = -(Xz*RNorm), (Xx*RNorm) -- cross y-axis with right and normalize 236 | local Ux, Uy, Uz = -Rz*(Rz*Xx-Rx*Xz), -(Rz*Rz)*Xy-(Rx*Rx)*Xy, Rx*(Rz*Xx-Rx*Xz) -- cross right and up and normalize. 237 | local UNorm = 1/((Ux*Ux)+(Uy*Uy)+(Uz*Uz))^0.5 -- inverse division and multiply this ratio rather than dividing each component 238 | Camera.CFrame = CFrame.new( 239 | Eye.X,Eye.Y,Eye.Z, 240 | Rx, -Xy*Rz, Ux*UNorm, 241 | 0, (Rz*Xx)-Rx*Xz, Uy*UNorm, 242 | Rz, Xy*Rx, Uz*UNorm 243 | ) 244 | end 245 | 246 | 247 | Module.HeadFollowCamera = function(_, Alpha) 248 | if not (Camera.CameraSubject:IsDescendantOf(Character) or Camera.CameraSubject:IsDescendantOf(Player)) then return end 249 | Alpha = Alpha or Configs.HeadFollowAlpha 250 | local ZColumn = -(Camera.CFrame.LookVector) 251 | ZColumn = ZColumn - Vector3.new(0, ZColumn.Y, 0) 252 | local XColumn = Vector3.yAxis:Cross(ZColumn) 253 | 254 | if Torso.CFrame:PointToObjectSpace(Head.Position+ZColumn).Z > 0 then 255 | Neck.C0 = Neck.C0:Lerp(NeckOriginC0 * CFrame.fromMatrix(Vector3.zero, XColumn, Vector3.yAxis, ZColumn), Alpha) 256 | Waist.C0 = Waist.C0:Lerp(WaistOriginC0 * CFrame.fromMatrix(Vector3.zero, XColumn*0.5, Vector3.yAxis, ZColumn), Alpha) 257 | end 258 | end 259 | 260 | 261 | Module.FaceCharacterToMouse = function(_, Alpha, GoalCF) 262 | if GoalCF then 263 | Root.CFrame = Root.CFrame:Lerp(GoalCF, Alpha or Configs.FaceCharacterAlpha) 264 | else 265 | local Eye = Root.Position 266 | local XAxis = Vector3.new(Mouse.Hit.Position.X, Root.Position.Y, Mouse.Hit.Position.Z)-Eye 267 | if (XAxis:Dot(XAxis) <= Module.Epsilon) then 268 | Root.CFrame = Root.CFrame:Lerp(CFrame.new( 269 | Eye.X, Eye.Y, Eye.Z, 270 | 1, 0, 0, 271 | 0, 1, 0, 272 | 0, 0, 1 273 | ), Alpha or Configs.FaceCharacterAlpha) 274 | end 275 | XAxis = XAxis.Unit 276 | local Xx, Xy, Xz = XAxis.X, XAxis.Y, XAxis.Z 277 | local RNorm = (((Xz*Xz)+(Xx*Xx))) -- R:Dot(R), our right vector 278 | if RNorm <= Module.Epsilon and math.abs(XAxis.Y) > 0 then 279 | Root.CFrame = Root.CFrame:Lerp(CFrame.fromMatrix( 280 | Eye, 281 | -math.sign(XAxis.Y)*Vector3.zAxis, 282 | Vector3.xAxis 283 | ), Alpha or Configs.FaceCharacterAlpha) 284 | end 285 | RNorm = 1/(RNorm^0.5) -- take the root of our squared norm and inverse division 286 | local Rx, Rz = -(Xz*RNorm), (Xx*RNorm) -- cross y-axis with right and normalize 287 | local Ux, Uy, Uz = -Rz*(Rz*Xx-Rx*Xz), -(Rz*Rz)*Xy-(Rx*Rx)*Xy, Rx*(Rz*Xx-Rx*Xz) -- cross right and up and normalize. 288 | local UNorm = 1/((Ux*Ux)+(Uy*Uy)+(Uz*Uz))^0.5 -- inverse division and multiply this ratio rather than dividing each component 289 | 290 | Root.CFrame = Root.CFrame:Lerp(CFrame.new( 291 | Eye.X,Eye.Y,Eye.Z, 292 | Rx, -Xy*Rz, Ux*UNorm, 293 | 0, (Rz*Xx)-Rx*Xz, Uy*UNorm, 294 | Rz, Xy*Rx, Uz*UNorm 295 | ), Alpha or Configs.FaceCharacterAlpha) 296 | end 297 | end 298 | 299 | 300 | Module.FollowMouse = function(Dt, Alpha, XOffset, YOffset) 301 | Alpha = Alpha or Configs.MouseAlpha 302 | XOffset = XOffset or Configs.MouseXOffset 303 | YOffset = YOffset or Configs.MouseYOffset 304 | DisableRobloxCamera() 305 | 306 | local Easing = Configs.MouseCameraEasingStyle and TweenService:GetValue(Configs.MouseCameraSmoothness, Configs.MouseCameraEasingStyle, Configs.MouseCameraEasingDirection or Enum.EasingDirection.Out) 307 | local Cosy, Siny = math.cos(XOffset), math.sin(XOffset) 308 | local Cosx, Sinx = math.cos(YOffset), math.sin(YOffset) 309 | local Goal = Head.CFrame * CFrame.new(0, Configs.AspectRatio.Y, Configs.AspectRatio.X) * CFrame.new( -- Given that we know the components of CFrame.new(0, Configs.AspectRatio.Y, Configs.AspectRatio.X), we can simplify the product 310 | 0, 0, 0, 311 | Cosy, Siny*Sinx, Siny*Cosx, 312 | 0, Cosx, -Sinx, 313 | -Siny, Cosy*Sinx, Cosy*Cosx 314 | ) 315 | 316 | if Easing then 317 | Camera.CFrame = Camera.CFrame:Lerp(Goal, Easing) 318 | else 319 | local a = Configs.MouseCameraSmoothness-1 320 | Camera.CFrame = Camera.CFrame:Lerp(Goal, (a*a*a*a*a)+1) 321 | end 322 | 323 | local P = Mouse.Hit.Position 324 | local Dis = (Head.CFrame.Position - P).Magnitude 325 | local Dif = Head.CFrame.Y-P.Y 326 | local X, Y = -math.atan(Dif/Dis)*0.5, (((Head.CFrame.Position-P).Unit):Cross(Torso.CFrame.LookVector)).Y 327 | 328 | -- rotation matrix -- 329 | local cosx, sinx = math.cos(X), math.sin(X) 330 | local cosy, siny = math.cos(Y), math.sin(Y) 331 | local R11, R12, R13 = 0, 0, siny 332 | local R21, R22, R23 = 0, 0, -cosy*sinx 333 | local R31, R32, R33 = 0, 0, cosx*cosy 334 | 335 | do -- Neck transformation matrix (OriginC0*rotation) -- 336 | local MotorC0 = Neck.C0 337 | local ax, ay, az, a11, a12, a13, a21, a22, a23, a31, a32, a33 = NeckOriginC0:GetComponents() 338 | local x, y, z = ax, ay, az 339 | R11, R12, R13 = 0, 0, a11*R13+a12*R23+a13*R33 340 | R21, R22, R23 = 0, 0, a21*R13+a22*R23+a23*R33 341 | R31, R32, R33 = 0, 0, a31*R13+a32*R23+a33*R33 342 | 343 | local ax, ay, az, a11, a12, a13, a21, a22, a23, a31, a32, a33 = MotorC0:GetComponents() 344 | local Determinant = a11*a22*a33+a12*a23*a31+a13*a21*a32-a11*a23*a32-a12*a21*a33-a13*a22*a31 345 | if Determinant ~= 0 then -- Fall to a slower case but still pretty nice little fast slerp trick 346 | local GoalPos = Vector3.new(x, y, z) 347 | local GoalLook = Vector3.new(-R13, -R23, -R33) 348 | local Theta = math.acos(MotorC0.lookVector:Dot(GoalLook)) 349 | 350 | if Theta < 0.01 then 351 | Neck.C0 = MotorC0 -- Because the rotation difference is practically the same 352 | else 353 | local Position = MotorC0.Position:Lerp(GoalPos, Alpha) 354 | local InvSin = 1/math.sin(Theta) 355 | local Rotation = math.sin((1-Alpha)*Theta)*InvSin*MotorC0.LookVector + math.sin(Alpha*Theta)*InvSin*GoalLook 356 | Neck.C0 = CFrame.lookAt( 357 | Position, 358 | Position + Rotation 359 | ) 360 | end 361 | else-- if det is 0, we don't have to calculute for matrix inverse; shortcut lerp. Otherwise, Roblox here probably benefits from SIMD hardware optimizations which typically use elimination methods 362 | local x, y, z = ax,ay, az 363 | R11, R12, R13 = 0, 0, a11*R13+a12*R23+a13*R33 -- convert goal to start's object space with inversion omitted 364 | R21, R22, R23 = 0, 0, a21*R13+a22*R23+a23*R33 365 | R31, R32, R33 = 0, 0, a31*R13+a32*R23+a33*R33 366 | 367 | if (R33 > 0) and math.acos(R23*(0.5/(math.sqrt(1+R33)))) <= Module.Epsilon then -- (R11+R22+R33)>0 (our trace) 368 | local Pos = MotorC0.Position + (Vector3.new(x, y+1, z) - MotorC0.Position) * Alpha*0.5 369 | Neck.C0 = CFrame.new ( 370 | Pos.X, Pos.Y, Pos.Z, 371 | R11, R12, R13, 372 | R21, R22, R23, 373 | R31, R32, R33 374 | ) 375 | else -- Best to just use :Lerp to save us here given the computations we already have made, our little fast slerp probably won't be able to save us at this point now... 376 | Neck.C0:Lerp(CFrame.new( 377 | 0, 0, 0, 378 | R11, R12, R13, 379 | R21, R22, R23, 380 | R31, R32, R33 381 | ), Alpha) 382 | end 383 | end 384 | end 385 | 386 | do -- Waist transformation matrix (OriginC0*rotation) -- 387 | cosy, siny = math.cos(Y*0.5), math.sin(Y*0.5) -- adjust rotation matrix for y/2 388 | R11, R12, R13 = 0, 0, siny 389 | R21, R22, R23 = 0, 0, -cosy*sinx 390 | R31, R32, R33 = 0, 0, cosx*cosy 391 | 392 | local MotorC0 = Waist.C0 393 | local ax, ay, az, a11, a12, a13, a21, a22, a23, a31, a32, a33 = WaistOriginC0:GetComponents() 394 | local x, y, z = ax, ay, az 395 | R11, R12, R13 = 0, 0, a11*R13+a12*R23+a13*R33 396 | R21, R22, R23 = 0, 0, a21*R13+a22*R23+a23*R33 397 | R31, R32, R33 = 0, 0, a31*R13+a32*R23+a33*R33 398 | 399 | local ax, ay, az, a11, a12, a13, a21, a22, a23, a31, a32, a33 = MotorC0:GetComponents() 400 | local Determinant = a11*a22*a33+a12*a23*a31+a13*a21*a32-a11*a23*a32-a12*a21*a33-a13*a22*a31 401 | if Determinant ~= 0 then 402 | local GoalPos = Vector3.new(x, y, z) 403 | local GoalLook = Vector3.new(-R13, -R23, -R33) 404 | local Theta = math.acos(MotorC0.lookVector:Dot(GoalLook)) -- LookVector of goal cframe 405 | 406 | if Theta < 0.01 then 407 | Waist.C0 = MotorC0 -- Because the rotation difference is practically the same 408 | return 409 | else 410 | local Position = MotorC0.Position:Lerp(GoalPos, Alpha) 411 | local InvSin = 1/math.sin(Theta) 412 | local Rotation = math.sin((1-Alpha)*Theta)*InvSin*MotorC0.LookVector + math.sin(Alpha*Theta)*InvSin*GoalLook 413 | Waist.C0 = CFrame.lookAt( 414 | Position, 415 | Position + Rotation 416 | ) 417 | return 418 | end 419 | else-- if det is 0, we don't have to calculute for matrix inverse; shortcut lerp. Otherwise, Roblox here probably benefits from SIMD hardware optimizations which typically use elimination methods 420 | local x, y, z = ax,ay, az 421 | R11, R12, R13 = 0, 0, a11*R13+a12*R23+a13*R33 -- convert goal to start's object space with inversion omitted 422 | R21, R22, R23 = 0, 0, a21*R13+a22*R23+a23*R33 423 | R31, R32, R33 = 0, 0, a31*R13+a32*R23+a33*R33 424 | 425 | if (R33 > 0 and math.acos(R23*(0.5/(math.sqrt(1+R33)))) <= Module.Epsilon) then -- (R11+R22+R33)>0 (our trace) 426 | local Pos = MotorC0.Position + (Vector3.new(x, y+1, z) - MotorC0.Position) * Alpha*0.5 427 | Waist.C0 = CFrame.new ( 428 | Pos.X, Pos.Y, Pos.Z, 429 | R11, R12, R13, 430 | R21, R22, R23, 431 | R31, R32, R33 432 | ) 433 | return 434 | else 435 | Waist.C0:Lerp(CFrame.new( -- Best to just use :Lerp to save us here given the computations we already have made, our little fast slerp probably won't be able to save us at this point now... 436 | 0, 0, 0, 437 | R11, R12, R13, 438 | R21, R22, R23, 439 | R31, R32, R33 440 | ), Alpha) 441 | return 442 | end 443 | end 444 | end 445 | end 446 | 447 | 448 | 449 | return Module 450 | --------------------------------------------------------------------------------