roblox-opencode 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/README.md +112 -122
  2. package/commands/setup-game.md +108 -108
  3. package/commands/sync-check.md +53 -53
  4. package/core/roblox-core.md +93 -93
  5. package/dist/server.js +189 -167
  6. package/package.json +35 -35
  7. package/skills/roblox-analytics/SKILL.md +277 -277
  8. package/skills/roblox-analytics/references/event-batcher.luau +75 -75
  9. package/skills/roblox-animation-vfx/SKILL.md +1325 -1325
  10. package/skills/roblox-architecture/SKILL.md +863 -863
  11. package/skills/roblox-architecture/references/combat-systems.md +1381 -1381
  12. package/skills/roblox-code-review/SKILL.md +686 -686
  13. package/skills/roblox-data/SKILL.md +889 -889
  14. package/skills/roblox-data/references/inventory-systems.md +1729 -1729
  15. package/skills/roblox-debug/SKILL.md +98 -98
  16. package/skills/roblox-gui/SKILL.md +1103 -1103
  17. package/skills/roblox-gui-fusion/SKILL.md +150 -150
  18. package/skills/roblox-gui-fusion/references/inventory.luau +427 -427
  19. package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -579
  20. package/skills/roblox-gui-fusion/references/shop.luau +411 -411
  21. package/skills/roblox-luau-mastery/SKILL.md +1519 -1519
  22. package/skills/roblox-monetization/SKILL.md +1084 -1084
  23. package/skills/roblox-monetization/references/process-receipt.luau +131 -131
  24. package/skills/roblox-networking/SKILL.md +669 -669
  25. package/skills/roblox-networking/references/remote-validator.luau +193 -193
  26. package/skills/roblox-publish-checklist/SKILL.md +127 -127
  27. package/skills/roblox-runtime/SKILL.md +753 -753
  28. package/skills/roblox-sharp-edges/SKILL.md +294 -294
  29. package/skills/roblox-sync/SKILL.md +126 -126
  30. package/skills/roblox-testing/SKILL.md +943 -943
  31. package/skills/roblox-tooling/SKILL.md +149 -149
  32. package/vendor/LICENSES/ProfileStore-LICENSE +201 -201
  33. package/vendor/LICENSES/RbxUtil-LICENSE +7 -7
  34. package/vendor/LICENSES/promise-LICENSE +20 -20
  35. package/vendor/LICENSES/t-LICENSE +21 -21
  36. package/vendor/LICENSES/testez-LICENSE +200 -200
  37. package/vendor/README.md +83 -83
  38. package/vendor/fusion/Animation/ExternalTime.luau +83 -83
  39. package/vendor/fusion/Animation/Spring.luau +321 -321
  40. package/vendor/fusion/Animation/Stopwatch.luau +127 -127
  41. package/vendor/fusion/Animation/Tween.luau +187 -187
  42. package/vendor/fusion/Animation/getTweenDuration.luau +27 -27
  43. package/vendor/fusion/Animation/getTweenRatio.luau +47 -47
  44. package/vendor/fusion/Animation/lerpType.luau +163 -163
  45. package/vendor/fusion/Animation/packType.luau +99 -99
  46. package/vendor/fusion/Animation/springCoefficients.luau +80 -80
  47. package/vendor/fusion/Animation/unpackType.luau +102 -102
  48. package/vendor/fusion/Colour/Oklab.luau +70 -70
  49. package/vendor/fusion/Colour/sRGB.luau +54 -54
  50. package/vendor/fusion/External.luau +167 -167
  51. package/vendor/fusion/ExternalDebug.luau +69 -69
  52. package/vendor/fusion/Graph/Observer.luau +113 -113
  53. package/vendor/fusion/Graph/castToGraph.luau +28 -28
  54. package/vendor/fusion/Graph/change.luau +80 -80
  55. package/vendor/fusion/Graph/depend.luau +32 -32
  56. package/vendor/fusion/Graph/evaluate.luau +55 -55
  57. package/vendor/fusion/Instances/Attribute.luau +57 -57
  58. package/vendor/fusion/Instances/AttributeChange.luau +46 -46
  59. package/vendor/fusion/Instances/AttributeOut.luau +63 -63
  60. package/vendor/fusion/Instances/Child.luau +21 -21
  61. package/vendor/fusion/Instances/Children.luau +147 -147
  62. package/vendor/fusion/Instances/Hydrate.luau +32 -32
  63. package/vendor/fusion/Instances/New.luau +52 -52
  64. package/vendor/fusion/Instances/OnChange.luau +49 -49
  65. package/vendor/fusion/Instances/OnEvent.luau +53 -53
  66. package/vendor/fusion/Instances/Out.luau +69 -69
  67. package/vendor/fusion/Instances/applyInstanceProps.luau +148 -148
  68. package/vendor/fusion/Instances/defaultProps.luau +194 -194
  69. package/vendor/fusion/LICENSE +21 -21
  70. package/vendor/fusion/Logging/formatError.luau +48 -48
  71. package/vendor/fusion/Logging/messages.luau +51 -51
  72. package/vendor/fusion/Logging/parseError.luau +24 -24
  73. package/vendor/fusion/Memory/checkLifetime.luau +133 -133
  74. package/vendor/fusion/Memory/deriveScope.luau +23 -23
  75. package/vendor/fusion/Memory/deriveScopeImpl.luau +44 -44
  76. package/vendor/fusion/Memory/doCleanup.luau +78 -78
  77. package/vendor/fusion/Memory/innerScope.luau +33 -33
  78. package/vendor/fusion/Memory/legacyCleanup.luau +17 -17
  79. package/vendor/fusion/Memory/needsDestruction.luau +16 -16
  80. package/vendor/fusion/Memory/poisonScope.luau +33 -33
  81. package/vendor/fusion/Memory/scopePool.luau +54 -54
  82. package/vendor/fusion/Memory/scoped.luau +26 -26
  83. package/vendor/fusion/Memory/whichLivesLonger.luau +74 -74
  84. package/vendor/fusion/RobloxExternal.luau +97 -97
  85. package/vendor/fusion/State/Computed.luau +138 -138
  86. package/vendor/fusion/State/For/Disassembly.luau +210 -210
  87. package/vendor/fusion/State/For/ForTypes.luau +30 -30
  88. package/vendor/fusion/State/For/init.luau +109 -109
  89. package/vendor/fusion/State/ForKeys.luau +93 -93
  90. package/vendor/fusion/State/ForPairs.luau +96 -96
  91. package/vendor/fusion/State/ForValues.luau +93 -93
  92. package/vendor/fusion/State/Value.luau +87 -87
  93. package/vendor/fusion/State/castToState.luau +25 -25
  94. package/vendor/fusion/State/peek.luau +30 -30
  95. package/vendor/fusion/Types.luau +314 -314
  96. package/vendor/fusion/Utility/Contextual.luau +90 -90
  97. package/vendor/fusion/Utility/Safe.luau +22 -22
  98. package/vendor/fusion/Utility/isSimilar.luau +29 -29
  99. package/vendor/fusion/Utility/merge.luau +35 -35
  100. package/vendor/fusion/Utility/nameOf.luau +34 -34
  101. package/vendor/fusion/Utility/never.luau +13 -13
  102. package/vendor/fusion/Utility/nicknames.luau +10 -10
  103. package/vendor/fusion/Utility/xtypeof.luau +26 -26
  104. package/vendor/fusion/init.luau +82 -82
  105. package/vendor/profilestore/init.luau +2242 -2242
  106. package/vendor/promise/init.luau +1982 -1982
  107. package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -25
  108. package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -228
  109. package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -269
  110. package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -223
  111. package/vendor/rbxutil/buffer-util/Types.luau +60 -60
  112. package/vendor/rbxutil/buffer-util/index.d.ts +153 -153
  113. package/vendor/rbxutil/buffer-util/init.luau +41 -41
  114. package/vendor/rbxutil/buffer-util/package.json +16 -16
  115. package/vendor/rbxutil/buffer-util/wally.toml +9 -9
  116. package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -232
  117. package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -156
  118. package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -109
  119. package/vendor/rbxutil/comm/Client/init.luau +135 -135
  120. package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -295
  121. package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -211
  122. package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -211
  123. package/vendor/rbxutil/comm/Server/init.luau +140 -140
  124. package/vendor/rbxutil/comm/Types.luau +18 -18
  125. package/vendor/rbxutil/comm/Util.luau +27 -27
  126. package/vendor/rbxutil/comm/init.luau +35 -35
  127. package/vendor/rbxutil/comm/wally.toml +13 -13
  128. package/vendor/rbxutil/component/init.luau +759 -759
  129. package/vendor/rbxutil/component/init.test.luau +311 -311
  130. package/vendor/rbxutil/component/wally.toml +14 -14
  131. package/vendor/rbxutil/concur/init.luau +542 -542
  132. package/vendor/rbxutil/concur/init.test.luau +364 -364
  133. package/vendor/rbxutil/concur/wally.toml +8 -8
  134. package/vendor/rbxutil/enum-list/init.luau +101 -101
  135. package/vendor/rbxutil/enum-list/init.test.luau +91 -91
  136. package/vendor/rbxutil/enum-list/wally.toml +8 -8
  137. package/vendor/rbxutil/find/index.d.ts +20 -20
  138. package/vendor/rbxutil/find/init.luau +44 -44
  139. package/vendor/rbxutil/find/package.json +17 -17
  140. package/vendor/rbxutil/find/wally.toml +8 -8
  141. package/vendor/rbxutil/input/Gamepad.luau +559 -559
  142. package/vendor/rbxutil/input/Keyboard.luau +124 -124
  143. package/vendor/rbxutil/input/Mouse.luau +278 -278
  144. package/vendor/rbxutil/input/PreferredInput.luau +91 -91
  145. package/vendor/rbxutil/input/Touch.luau +120 -120
  146. package/vendor/rbxutil/input/init.luau +33 -33
  147. package/vendor/rbxutil/input/wally.toml +12 -12
  148. package/vendor/rbxutil/loader/index.d.ts +15 -15
  149. package/vendor/rbxutil/loader/init.luau +137 -137
  150. package/vendor/rbxutil/loader/wally.toml +8 -8
  151. package/vendor/rbxutil/log/index.d.ts +38 -38
  152. package/vendor/rbxutil/log/init.luau +746 -746
  153. package/vendor/rbxutil/log/wally.toml +8 -8
  154. package/vendor/rbxutil/net/init.luau +190 -190
  155. package/vendor/rbxutil/net/wally.toml +8 -8
  156. package/vendor/rbxutil/option/index.d.ts +44 -44
  157. package/vendor/rbxutil/option/init.luau +489 -489
  158. package/vendor/rbxutil/option/init.test.luau +342 -342
  159. package/vendor/rbxutil/option/wally.toml +8 -8
  160. package/vendor/rbxutil/pid/index.d.ts +53 -53
  161. package/vendor/rbxutil/pid/init.luau +195 -195
  162. package/vendor/rbxutil/pid/package.json +16 -16
  163. package/vendor/rbxutil/pid/wally.toml +9 -9
  164. package/vendor/rbxutil/quaternion/index.d.ts +117 -117
  165. package/vendor/rbxutil/quaternion/init.luau +570 -570
  166. package/vendor/rbxutil/quaternion/package.json +16 -16
  167. package/vendor/rbxutil/quaternion/wally.toml +9 -9
  168. package/vendor/rbxutil/query/index.d.ts +43 -43
  169. package/vendor/rbxutil/query/init.luau +117 -117
  170. package/vendor/rbxutil/query/package.json +18 -18
  171. package/vendor/rbxutil/query/wally.toml +9 -9
  172. package/vendor/rbxutil/sequent/index.d.ts +28 -28
  173. package/vendor/rbxutil/sequent/init.luau +340 -340
  174. package/vendor/rbxutil/sequent/package.json +16 -16
  175. package/vendor/rbxutil/sequent/wally.toml +9 -9
  176. package/vendor/rbxutil/ser/init.luau +175 -175
  177. package/vendor/rbxutil/ser/init.test.luau +50 -50
  178. package/vendor/rbxutil/ser/wally.toml +11 -11
  179. package/vendor/rbxutil/shake/index.d.ts +36 -36
  180. package/vendor/rbxutil/shake/init.luau +532 -532
  181. package/vendor/rbxutil/shake/init.test.luau +267 -267
  182. package/vendor/rbxutil/shake/package.json +16 -16
  183. package/vendor/rbxutil/shake/wally.toml +9 -9
  184. package/vendor/rbxutil/signal/index.d.ts +100 -100
  185. package/vendor/rbxutil/signal/init.luau +432 -432
  186. package/vendor/rbxutil/signal/init.test.luau +190 -190
  187. package/vendor/rbxutil/signal/package.json +17 -17
  188. package/vendor/rbxutil/signal/wally.toml +9 -9
  189. package/vendor/rbxutil/silo/TableWatcher.luau +65 -65
  190. package/vendor/rbxutil/silo/Util.luau +55 -55
  191. package/vendor/rbxutil/silo/init.luau +338 -338
  192. package/vendor/rbxutil/silo/init.test.luau +215 -215
  193. package/vendor/rbxutil/silo/wally.toml +8 -8
  194. package/vendor/rbxutil/spring/index.d.ts +40 -40
  195. package/vendor/rbxutil/spring/init.luau +97 -97
  196. package/vendor/rbxutil/spring/package.json +17 -17
  197. package/vendor/rbxutil/spring/wally.toml +8 -8
  198. package/vendor/rbxutil/stream/index.d.ts +88 -88
  199. package/vendor/rbxutil/stream/init.luau +597 -597
  200. package/vendor/rbxutil/stream/package.json +18 -18
  201. package/vendor/rbxutil/stream/wally.toml +9 -9
  202. package/vendor/rbxutil/streamable/Streamable.luau +202 -202
  203. package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -80
  204. package/vendor/rbxutil/streamable/init.luau +8 -8
  205. package/vendor/rbxutil/streamable/wally.toml +12 -12
  206. package/vendor/rbxutil/symbol/init.luau +56 -56
  207. package/vendor/rbxutil/symbol/init.test.luau +37 -37
  208. package/vendor/rbxutil/symbol/wally.toml +8 -8
  209. package/vendor/rbxutil/table-util/init.luau +938 -938
  210. package/vendor/rbxutil/table-util/init.test.luau +439 -439
  211. package/vendor/rbxutil/task-queue/index.d.ts +27 -27
  212. package/vendor/rbxutil/task-queue/init.luau +97 -97
  213. package/vendor/rbxutil/task-queue/wally.toml +8 -8
  214. package/vendor/rbxutil/timer/index.d.ts +81 -81
  215. package/vendor/rbxutil/timer/init.luau +249 -249
  216. package/vendor/rbxutil/timer/init.test.luau +73 -73
  217. package/vendor/rbxutil/timer/wally.toml +11 -11
  218. package/vendor/rbxutil/tree/index.d.ts +15 -15
  219. package/vendor/rbxutil/tree/init.luau +137 -137
  220. package/vendor/rbxutil/tree/wally.toml +8 -8
  221. package/vendor/rbxutil/trove/index.d.ts +46 -46
  222. package/vendor/rbxutil/trove/init.luau +787 -787
  223. package/vendor/rbxutil/trove/init.test.luau +203 -203
  224. package/vendor/rbxutil/trove/wally.toml +8 -8
  225. package/vendor/rbxutil/typed-remote/init.luau +196 -196
  226. package/vendor/rbxutil/typed-remote/wally.toml +8 -8
  227. package/vendor/rbxutil/wait-for/index.d.ts +17 -17
  228. package/vendor/rbxutil/wait-for/init.luau +257 -257
  229. package/vendor/rbxutil/wait-for/init.test.luau +182 -182
  230. package/vendor/rbxutil/wait-for/wally.toml +11 -11
  231. package/vendor/t/t.lua +1350 -1350
  232. package/vendor/testez/Context.lua +26 -26
  233. package/vendor/testez/Expectation.lua +311 -311
  234. package/vendor/testez/ExpectationContext.lua +38 -38
  235. package/vendor/testez/LifecycleHooks.lua +89 -89
  236. package/vendor/testez/Reporters/TeamCityReporter.lua +101 -101
  237. package/vendor/testez/Reporters/TextReporter.lua +105 -105
  238. package/vendor/testez/Reporters/TextReporterQuiet.lua +96 -96
  239. package/vendor/testez/TestBootstrap.lua +146 -146
  240. package/vendor/testez/TestEnum.lua +27 -27
  241. package/vendor/testez/TestPlan.lua +304 -304
  242. package/vendor/testez/TestPlanner.lua +39 -39
  243. package/vendor/testez/TestResults.lua +111 -111
  244. package/vendor/testez/TestRunner.lua +188 -188
  245. package/vendor/testez/TestSession.lua +243 -243
  246. package/vendor/testez/init.lua +39 -39
@@ -1,570 +1,570 @@
1
- --!strict
2
-
3
- --[[
4
- Algorithmic credit: https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm
5
- ]]
6
-
7
- --[=[
8
- @within Quaternion
9
- @interface Quaternion
10
- .X number
11
- .Y number
12
- .Z number
13
- .W number
14
-
15
- Similar to Vector3s, Quaternions are immutable. You cannot manually set the individual properties
16
- of a Quaternion. Instead, a new Quaternion must first be constructed.
17
- ]=]
18
- export type Quaternion = {
19
- X: number,
20
- Y: number,
21
- Z: number,
22
- W: number,
23
-
24
- Dot: (self: Quaternion, other: Quaternion) -> number,
25
- Slerp: (self: Quaternion, other: Quaternion, alpha: number) -> Quaternion,
26
- Angle: (self: Quaternion, other: Quaternion) -> number,
27
- RotateTowards: (self: Quaternion, other: Quaternion, maxRadiansDelta: number) -> Quaternion,
28
- ToCFrame: (self: Quaternion, position: Vector3?) -> CFrame,
29
- ToEulerAngles: (self: Quaternion) -> Vector3,
30
- ToAxisAngle: (self: Quaternion) -> (Vector3, number),
31
- Inverse: (self: Quaternion) -> Quaternion,
32
- Conjugate: (self: Quaternion) -> Quaternion,
33
- Normalize: (self: Quaternion) -> Quaternion,
34
- Magnitude: (self: Quaternion) -> number,
35
- SqrMagnitude: (self: Quaternion) -> number,
36
- }
37
-
38
- local EPSILON = 1e-5
39
-
40
- --[=[
41
- @class Quaternion
42
- Represents a Quaternion. Quaternions are 4D structs that represent rotations
43
- in 3D space. Quaternions are often used in 3D graphics to avoid the ambiguity
44
- that comes with Euler angles (e.g. gimbal lock).
45
-
46
- Roblox represents the transformation of an object via CFrame values, which are
47
- matrices that hold positional and rotational information. Quaternions can be converted
48
- to and from CFrame values through the `ToCFrame` method and `cframe` constructor.
49
- ]=]
50
- local Quaternion = {}
51
- Quaternion.__index = Quaternion
52
-
53
- --[=[
54
- Constructs a Quaternion.
55
-
56
- :::caution
57
- The `new` constructor assumes the given arguments represent a proper Quaternion. This
58
- constructor should only be used if you really know what you're doing.
59
- :::
60
- ]=]
61
- function Quaternion.new(x: number, y: number, z: number, w: number): Quaternion
62
- local self = setmetatable({
63
- X = x,
64
- Y = y,
65
- Z = z,
66
- W = w,
67
- }, Quaternion) :: any
68
-
69
- table.freeze(self)
70
-
71
- return self
72
- end
73
-
74
- --[=[
75
- Constructs a Quaternion from Euler angles (radians).
76
-
77
- ```lua
78
- -- Quaternion rotated 45 degrees on the Y axis:
79
- local quat = Quaternion.euler(0, math.rad(45), 0)
80
- ```
81
- ]=]
82
- function Quaternion.euler(x: number, y: number, z: number): Quaternion
83
- local cx = math.cos(x * 0.5)
84
- local cy = math.cos(y * 0.5)
85
- local cz = math.cos(z * 0.5)
86
- local sx = math.sin(x * 0.5)
87
- local sy = math.sin(y * 0.5)
88
- local sz = math.sin(z * 0.5)
89
-
90
- return Quaternion.new(
91
- cx * sy * sz + cy * cz * sx,
92
- cx * cz * sy - cy * sx * sz,
93
- cx * cy * sz - cz * sx * sy,
94
- sx * sy * sz + cx * cy * cz
95
- )
96
- end
97
-
98
- --[=[
99
- Constructs a Quaternion representing a rotation of `angle` radians around `axis`.
100
-
101
- ```lua
102
- -- Quaternion rotated 45 degrees on the Y axis:
103
- local quat = Quaternion.axisAngle(Vector3.yAxis, math.rad(45))
104
- ```
105
- ]=]
106
- function Quaternion.axisAngle(axis: Vector3, angle: number): Quaternion
107
- local halfAngle = angle / 2
108
- local sin = math.sin(halfAngle)
109
-
110
- return Quaternion.new(sin * axis.X, sin * axis.Y, sin * axis.Z, math.cos(halfAngle))
111
- end
112
-
113
- --[=[
114
- Constructs a Quaternion representing a rotation facing `forward` direction, where
115
- `upwards` represents the upwards direction (this defaults to `Vector3.yAxis`).
116
-
117
- ```lua
118
- -- Create a quaternion facing the same direction as the camera:
119
- local camCf = workspace.CurrentCamera.CFrame
120
- local quat = Quaternion.lookRotation(camCf.LookVector, camCf.UpVector)
121
- ```
122
- ]=]
123
- function Quaternion.lookRotation(forward: Vector3, upwards: Vector3?): Quaternion
124
- local up = if upwards == nil then Vector3.yAxis else upwards.Unit
125
- forward = forward.Unit
126
-
127
- local v1 = forward
128
- local v2 = up:Cross(v1)
129
- local v3 = v1:Cross(v2)
130
-
131
- local m00 = v2.X
132
- local m01 = v2.Y
133
- local m02 = v2.Z
134
- local m10 = v3.X
135
- local m11 = v3.Y
136
- local m12 = v3.Z
137
- local m20 = v1.X
138
- local m21 = v1.Y
139
- local m22 = v1.Z
140
-
141
- local n8 = m00 + m11 + m22
142
-
143
- if n8 > 0 then
144
- local n = math.sqrt(n8 + 1)
145
-
146
- return Quaternion.new((m12 - m21) * n, (m20 - m02) * n, (m01 - m10) * n, n * 0.5)
147
- end
148
-
149
- if m00 >= m11 and m00 >= m22 then
150
- local n7 = math.sqrt(((1 + m00) - m11) - m22)
151
- local n4 = 0.5 / n7
152
-
153
- return Quaternion.new(0.5 * n7, (m01 + m10) * n4, (m02 + m20) * n4, (m12 - m21) * n4)
154
- end
155
-
156
- if m11 > m22 then
157
- local n6 = math.sqrt(((1 + m11) - m00) - m22)
158
- local n3 = 0.5 / n6
159
-
160
- return Quaternion.new((m10 + m01) * n3, 0.5 * n6, (m21 + m12) * n3, (m20 - m02) * n3)
161
- end
162
-
163
- local n5 = math.sqrt(((1 + m22) - m00) - m11)
164
- local n2 = 0.5 / n5
165
-
166
- return Quaternion.new((m20 + m02) * n2, (m21 + m12) * n2, 0.5 * n5, (m01 - m10) * n2)
167
- end
168
-
169
- --[=[
170
- Constructs a Quaternion from the rotation components of the given `cframe`.
171
-
172
- This method ortho-normalizes the CFrame value, so there is no need to do this yourself
173
- before calling the function.
174
-
175
- ```lua
176
- -- Create a Quaternion representing the rotational CFrame of a part:
177
- local quat = Quaternion.cframe(somePart.CFrame)
178
- ```
179
- ]=]
180
- function Quaternion.cframe(cframe: CFrame): Quaternion
181
- local _, _, _, m00, m01, m02, m10, m11, m12, m20, m21, m22 = cframe:Orthonormalize():GetComponents()
182
-
183
- local x, y, z, w
184
-
185
- local trace = m00 + m11 + m22
186
- if trace > 0 then
187
- local s = math.sqrt(trace + 1) * 2
188
- x = (m21 - m12) / s
189
- y = (m02 - m20) / s
190
- z = (m10 - m01) / s
191
- w = 0.25 * s
192
- elseif m00 > m11 and m00 > m22 then
193
- local s = math.sqrt(1 + m00 - m11 - m22) * 2
194
- x = 0.25 * s
195
- y = (m01 + m10) / s
196
- z = (m02 + m20) / s
197
- w = (m21 - m12) / s
198
- elseif m11 > m22 then
199
- local s = math.sqrt(1 + m11 - m00 - m22) * 2
200
- x = (m01 + m10) / s
201
- y = 0.25 * s
202
- z = (m12 + m21) / s
203
- w = (m02 - m20) / s
204
- else
205
- local s = math.sqrt(1 + m22 - m00 - m11) * 2
206
- x = (m02 + m20) / s
207
- y = (m12 + m21) / s
208
- z = 0.25 * s
209
- w = (m10 - m01) / s
210
- end
211
-
212
- return Quaternion.new(x, y, z, w)
213
- end
214
-
215
- --[=[
216
- @method Dot
217
- @within Quaternion
218
- @param other Quaternion
219
- @return number
220
-
221
- Calculates the dot product between the two Quaternions.
222
-
223
- ```lua
224
- local dot = quatA:Dot(quatB)
225
- ```
226
- ]=]
227
- function Quaternion.Dot(self: Quaternion, other: Quaternion): number
228
- return self.X * other.X + self.Y * other.Y + self.Z * other.Z + self.W * other.W
229
- end
230
-
231
- --[=[
232
- @method Slerp
233
- @within Quaternion
234
- @param other Quaternion
235
- @param t number
236
- @return Quaternion
237
-
238
- Calculates a spherical interpolation between the two Quaternions. Parameter `t` represents
239
- the percentage between the two rotations, from a range of `[0, 1]`.
240
-
241
- Spherical interpolation is great for smoothing or animating between quaternions.
242
-
243
- ```lua
244
- local midWay = quatA:Slerp(quatB, 0.5)
245
- ```
246
- ]=]
247
- function Quaternion.Slerp(a: Quaternion, b: Quaternion, t: number): Quaternion
248
- local cosOmega = a:Dot(b)
249
- local flip = false
250
-
251
- if cosOmega < 0 then
252
- flip = true
253
- cosOmega = -cosOmega
254
- end
255
-
256
- local s1, s2
257
-
258
- if cosOmega > (1 - EPSILON) then
259
- s1 = 1 - t
260
- s2 = if flip then -t else t
261
- else
262
- local omega = math.acos(cosOmega)
263
- local invSinOmega = 1 / math.sin(omega)
264
-
265
- s1 = math.sin((1 - t) * omega) * invSinOmega
266
- s2 = if flip then -math.sin(t * omega) * invSinOmega else math.sin(t * omega) * invSinOmega
267
- end
268
-
269
- return Quaternion.new(s1 * a.X + s2 * b.X, s1 * a.Y + s2 * b.Y, s1 * a.Z + s2 * b.Z, s1 * a.W + s2 * b.W)
270
- end
271
-
272
- --[=[
273
- @method Angle
274
- @within Quaternion
275
- @param other Quaternion
276
- @return number
277
-
278
- Calculates the angle (radians) between the two Quaternions.
279
-
280
- ```lua
281
- local angle = quatA:Angle(quatB)
282
- ```
283
- ]=]
284
- function Quaternion.Angle(self: Quaternion, other: Quaternion): number
285
- local dot = math.min(math.abs(self:Dot(other)), 1)
286
- local angle = if dot > (1 - EPSILON) then 0 else math.acos(dot) * 2
287
-
288
- return angle
289
- end
290
-
291
- --[=[
292
- @method RotateTowards
293
- @within Quaternion
294
- @param other Quaternion
295
- @param maxRadiansDelta number
296
- @return Quaternion
297
-
298
- Constructs a new Quaternion that rotates from this Quaternion to the `other` quaternion, with a maximum
299
- rotation of `maxRadiansDelta`. Internally, this calls `Slerp`, but limits the movement to `maxRadiansDelta`.
300
-
301
- ```lua
302
- -- Rotate from quatA to quatB, but only by 10 degrees:
303
- local q = quatA:RotateTowards(quatB, math.rad(10))
304
- ```
305
- ]=]
306
- function Quaternion.RotateTowards(self: Quaternion, other: Quaternion, maxRadiansDelta: number): Quaternion
307
- local angle = self:Angle(other)
308
-
309
- if angle == 0 then
310
- return self
311
- end
312
-
313
- local alpha = maxRadiansDelta / angle
314
-
315
- return self:Slerp(other, alpha)
316
- end
317
-
318
- --[=[
319
- @method ToCFrame
320
- @within Quaternion
321
- @param position Vector3?
322
- @return CFrame
323
-
324
- Constructs a CFrame value representing the Quaternion. An optional `position` Vector can be given to
325
- represent the position of the CFrame in 3D space. This defaults to `Vector3.zero`.
326
-
327
- ```lua
328
- -- Construct a CFrame from the quaternion, where the position will be at the origin point:
329
- local cf = quat:ToCFrame()
330
-
331
- -- Construct a CFrame with a given position:
332
- local cf = quat:ToCFrame(someVector3)
333
-
334
- -- e.g., set a part's CFrame:
335
- local part = workspace.Part
336
- local quat = Quaternion.axisAngle(Vector3.yAxis, math.rad(45))
337
- local cframe = quat:ToCFrame(part.Position) -- Construct CFrame with a positional component
338
- part.CFrame = cframe
339
- ```
340
- ]=]
341
- function Quaternion.ToCFrame(self: Quaternion, position: Vector3?): CFrame
342
- local pos = if position == nil then Vector3.zero else position
343
-
344
- return CFrame.new(pos.X, pos.Y, pos.Z, self.X, self.Y, self.Z, self.W)
345
- end
346
-
347
- --[=[
348
- @method ToEulerAngles
349
- @within Quaternion
350
- @return Vector3
351
-
352
- Calculates the Euler angles (radians) that represent the Quaternion.
353
-
354
- ```lua
355
- local euler = quat:ToEulerAngles()
356
- print(euler.X, euler.Y, euler.Z)
357
- ```
358
- ]=]
359
- function Quaternion.ToEulerAngles(self: Quaternion): Vector3
360
- local test = self.X * self.Y + self.Z * self.W
361
- if test > 0.49999 then
362
- return Vector3.new(0, 2 * math.atan2(self.X, self.W), math.pi / 2)
363
- elseif test < -0.49999 then
364
- return Vector3.new(0, -2 * math.atan2(self.X, self.W), -math.pi / 2)
365
- end
366
-
367
- local sqx = self.X * self.X
368
- local sqy = self.Y * self.Y
369
- local sqz = self.Z * self.Z
370
-
371
- return Vector3.new(
372
- math.atan2(2 * self.X * self.W - 2 * self.Y * self.Z, 1 - 2 * sqx - 2 * sqz),
373
- math.atan2(2 * self.Y * self.W - 2 * self.X * self.Z, 1 - 2 * sqy - 2 * sqz),
374
- math.asin(2 * test)
375
- )
376
- end
377
-
378
- --[=[
379
- @method ToAxisAngle
380
- @within Quaternion
381
- @return (Vector3, number)
382
-
383
- Calculates the axis and angle representing the Quaternion.
384
-
385
- ```lua
386
- local axis, angle = quat:ToAxisAngle()
387
- ```
388
- ]=]
389
- function Quaternion.ToAxisAngle(self: Quaternion): (Vector3, number)
390
- local scale = math.sqrt(self.X * self.X + self.Y * self.Y + self.Z * self.Z)
391
-
392
- if math.abs(scale) < EPSILON or self.W > 1 or self.W < -1 then
393
- return Vector3.yAxis, 0
394
- end
395
-
396
- local invScale = 1 / scale
397
-
398
- local axis = Vector3.new(self.X * invScale, self.Y * invScale, self.Z * invScale)
399
- local angle = 2 * math.acos(self.W)
400
-
401
- return axis, angle
402
- end
403
-
404
- --[=[
405
- @method Inverse
406
- @within Quaternion
407
- @return Quaternion
408
-
409
- Returns the inverse of the Quaternion.
410
-
411
- ```lua
412
- local quatInverse = quat:Inverse()
413
- ```
414
- ]=]
415
- function Quaternion.Inverse(self: Quaternion): Quaternion
416
- local dot = self:Dot(self)
417
- local invNormal = 1 / dot
418
- return Quaternion.new(-self.X * invNormal, -self.Y * invNormal, -self.Z * invNormal, self.W * invNormal)
419
- end
420
-
421
- --[=[
422
- @method Conjugate
423
- @within Quaternion
424
- @return Quaternion
425
-
426
- Returns the conjugate of the Quaternion. This is equal to `Quaternion.new(-X, -Y, -Z, W)`.
427
-
428
- ```lua
429
- local quatConjugate = quat:Conjugate()
430
- ```
431
- ]=]
432
- function Quaternion.Conjugate(self: Quaternion): Quaternion
433
- return Quaternion.new(-self.X, -self.Y, -self.Z, self.W)
434
- end
435
-
436
- --[=[
437
- @method Normalize
438
- @within Quaternion
439
- @return Quaternion
440
-
441
- Returns the normalized representation of the Quaternion.
442
-
443
- ```lua
444
- local quatNormalized = quat:Normalize()
445
- ```
446
- ]=]
447
- function Quaternion.Normalize(self: Quaternion): Quaternion
448
- local magnitude = self:Magnitude()
449
-
450
- if magnitude < EPSILON then
451
- return Quaternion.identity
452
- end
453
-
454
- return Quaternion.new(self.X / magnitude, self.Y / magnitude, self.Z / magnitude, self.W / magnitude)
455
- end
456
-
457
- --[=[
458
- @method Magnitude
459
- @within Quaternion
460
- @return number
461
-
462
- Calculates the magnitude of the Quaternion.
463
-
464
- ```lua
465
- local magnitude = quat:Magnitude()
466
- ```
467
- ]=]
468
- function Quaternion.Magnitude(self: Quaternion): number
469
- return math.sqrt(self:Dot(self))
470
- end
471
-
472
- --[=[
473
- @method SqrMagnitude
474
- @within Quaternion
475
- @return number
476
-
477
- Calculates the square magnitude of the Quaternion.
478
-
479
- ```lua
480
- local squareMagnitude = quat:Magnitude()
481
- ```
482
- ]=]
483
- function Quaternion.SqrMagnitude(self: Quaternion): number
484
- return self:Dot(self)
485
- end
486
-
487
- function Quaternion._MulVector3(self: Quaternion, other: Vector3): Vector3
488
- local x = self.X * 2
489
- local y = self.Y * 2
490
- local z = self.Z * 2
491
-
492
- local xx = self.X * x
493
- local yy = self.Y * y
494
- local zz = self.Z * z
495
- local xy = self.X * y
496
- local xz = self.X * z
497
- local yz = self.Y * z
498
- local wx = self.W * x
499
- local wy = self.W * y
500
- local wz = self.W * z
501
-
502
- return Vector3.new(
503
- (1 - (yy + zz)) * other.X + (xy - wz) * other.Y + (xz + wy) * other.Z,
504
- (xy + wz) * other.X + (1 - (xx + zz)) * other.Y + (yz - wx) * other.Z,
505
- (xz - wy) * other.X + (yz + wx) * other.Y + (1 - (xx + yy)) * other.Z
506
- )
507
- end
508
-
509
- function Quaternion._MulQuaternion(self: Quaternion, other: Quaternion): Quaternion
510
- return Quaternion.new(
511
- self.W * other.X + self.X * other.W + self.Y * other.Z - self.Z * other.Y,
512
- self.W * other.Y + self.Y * other.W + self.Z * other.X - self.X * other.Z,
513
- self.W * other.Z + self.Z * other.W + self.X * other.Y - self.Y * other.X,
514
- self.W * other.W - self.X * other.X - self.Y * other.Y - self.Z * other.Z
515
- )
516
- end
517
-
518
- --[=[
519
- Multiplication metamethod. A Quaternion can be multiplied with another Quaternion or
520
- a Vector3.
521
-
522
- ```lua
523
- local quat = quatA * quatB
524
- local vec = quatA * vecA
525
- ```
526
- ]=]
527
- function Quaternion.__mul(self: Quaternion, other: Quaternion | Vector3): Quaternion | Vector3
528
- local t = typeof(other)
529
- if t == "Vector3" then
530
- return Quaternion._MulVector3(self, other :: any)
531
- elseif t == "table" and getmetatable(other :: any) == Quaternion then
532
- return Quaternion._MulQuaternion(self, (other :: any) :: Quaternion)
533
- else
534
- error(`cannot multiply quaternion with type {t}`, 2)
535
- end
536
- end
537
-
538
- function Quaternion.__unm(self: Quaternion): Quaternion
539
- return Quaternion.new(-self.X, -self.Y, -self.Z, -self.W)
540
- end
541
-
542
- function Quaternion.__eq(self: Quaternion, other: Quaternion): boolean
543
- return self.X == other.X and self.Y == other.Y and self.Z == other.Z and self.W == other.W
544
- end
545
-
546
- function Quaternion.__tostring(self: Quaternion): string
547
- return `{self.X}, {self.Y}, {self.Z}, {self.W}`
548
- end
549
-
550
- -- Kind of support roblox-ts multiplication (not really, but follows the same pattern):
551
- Quaternion.mul = Quaternion.__mul
552
-
553
- --[=[
554
- @prop identity Quaternion
555
- @readonly
556
- @within Quaternion
557
-
558
- Identity Quaternion. Equal to `Quaternion.new(0, 0, 0, 1)`.
559
- ]=]
560
- Quaternion.identity = Quaternion.new(0, 0, 0, 1)
561
-
562
- return {
563
- new = Quaternion.new,
564
- euler = Quaternion.euler,
565
- axisAngle = Quaternion.axisAngle,
566
- lookRotation = Quaternion.lookRotation,
567
- cframe = Quaternion.cframe,
568
-
569
- identity = Quaternion.identity,
570
- }
1
+ --!strict
2
+
3
+ --[[
4
+ Algorithmic credit: https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm
5
+ ]]
6
+
7
+ --[=[
8
+ @within Quaternion
9
+ @interface Quaternion
10
+ .X number
11
+ .Y number
12
+ .Z number
13
+ .W number
14
+
15
+ Similar to Vector3s, Quaternions are immutable. You cannot manually set the individual properties
16
+ of a Quaternion. Instead, a new Quaternion must first be constructed.
17
+ ]=]
18
+ export type Quaternion = {
19
+ X: number,
20
+ Y: number,
21
+ Z: number,
22
+ W: number,
23
+
24
+ Dot: (self: Quaternion, other: Quaternion) -> number,
25
+ Slerp: (self: Quaternion, other: Quaternion, alpha: number) -> Quaternion,
26
+ Angle: (self: Quaternion, other: Quaternion) -> number,
27
+ RotateTowards: (self: Quaternion, other: Quaternion, maxRadiansDelta: number) -> Quaternion,
28
+ ToCFrame: (self: Quaternion, position: Vector3?) -> CFrame,
29
+ ToEulerAngles: (self: Quaternion) -> Vector3,
30
+ ToAxisAngle: (self: Quaternion) -> (Vector3, number),
31
+ Inverse: (self: Quaternion) -> Quaternion,
32
+ Conjugate: (self: Quaternion) -> Quaternion,
33
+ Normalize: (self: Quaternion) -> Quaternion,
34
+ Magnitude: (self: Quaternion) -> number,
35
+ SqrMagnitude: (self: Quaternion) -> number,
36
+ }
37
+
38
+ local EPSILON = 1e-5
39
+
40
+ --[=[
41
+ @class Quaternion
42
+ Represents a Quaternion. Quaternions are 4D structs that represent rotations
43
+ in 3D space. Quaternions are often used in 3D graphics to avoid the ambiguity
44
+ that comes with Euler angles (e.g. gimbal lock).
45
+
46
+ Roblox represents the transformation of an object via CFrame values, which are
47
+ matrices that hold positional and rotational information. Quaternions can be converted
48
+ to and from CFrame values through the `ToCFrame` method and `cframe` constructor.
49
+ ]=]
50
+ local Quaternion = {}
51
+ Quaternion.__index = Quaternion
52
+
53
+ --[=[
54
+ Constructs a Quaternion.
55
+
56
+ :::caution
57
+ The `new` constructor assumes the given arguments represent a proper Quaternion. This
58
+ constructor should only be used if you really know what you're doing.
59
+ :::
60
+ ]=]
61
+ function Quaternion.new(x: number, y: number, z: number, w: number): Quaternion
62
+ local self = setmetatable({
63
+ X = x,
64
+ Y = y,
65
+ Z = z,
66
+ W = w,
67
+ }, Quaternion) :: any
68
+
69
+ table.freeze(self)
70
+
71
+ return self
72
+ end
73
+
74
+ --[=[
75
+ Constructs a Quaternion from Euler angles (radians).
76
+
77
+ ```lua
78
+ -- Quaternion rotated 45 degrees on the Y axis:
79
+ local quat = Quaternion.euler(0, math.rad(45), 0)
80
+ ```
81
+ ]=]
82
+ function Quaternion.euler(x: number, y: number, z: number): Quaternion
83
+ local cx = math.cos(x * 0.5)
84
+ local cy = math.cos(y * 0.5)
85
+ local cz = math.cos(z * 0.5)
86
+ local sx = math.sin(x * 0.5)
87
+ local sy = math.sin(y * 0.5)
88
+ local sz = math.sin(z * 0.5)
89
+
90
+ return Quaternion.new(
91
+ cx * sy * sz + cy * cz * sx,
92
+ cx * cz * sy - cy * sx * sz,
93
+ cx * cy * sz - cz * sx * sy,
94
+ sx * sy * sz + cx * cy * cz
95
+ )
96
+ end
97
+
98
+ --[=[
99
+ Constructs a Quaternion representing a rotation of `angle` radians around `axis`.
100
+
101
+ ```lua
102
+ -- Quaternion rotated 45 degrees on the Y axis:
103
+ local quat = Quaternion.axisAngle(Vector3.yAxis, math.rad(45))
104
+ ```
105
+ ]=]
106
+ function Quaternion.axisAngle(axis: Vector3, angle: number): Quaternion
107
+ local halfAngle = angle / 2
108
+ local sin = math.sin(halfAngle)
109
+
110
+ return Quaternion.new(sin * axis.X, sin * axis.Y, sin * axis.Z, math.cos(halfAngle))
111
+ end
112
+
113
+ --[=[
114
+ Constructs a Quaternion representing a rotation facing `forward` direction, where
115
+ `upwards` represents the upwards direction (this defaults to `Vector3.yAxis`).
116
+
117
+ ```lua
118
+ -- Create a quaternion facing the same direction as the camera:
119
+ local camCf = workspace.CurrentCamera.CFrame
120
+ local quat = Quaternion.lookRotation(camCf.LookVector, camCf.UpVector)
121
+ ```
122
+ ]=]
123
+ function Quaternion.lookRotation(forward: Vector3, upwards: Vector3?): Quaternion
124
+ local up = if upwards == nil then Vector3.yAxis else upwards.Unit
125
+ forward = forward.Unit
126
+
127
+ local v1 = forward
128
+ local v2 = up:Cross(v1)
129
+ local v3 = v1:Cross(v2)
130
+
131
+ local m00 = v2.X
132
+ local m01 = v2.Y
133
+ local m02 = v2.Z
134
+ local m10 = v3.X
135
+ local m11 = v3.Y
136
+ local m12 = v3.Z
137
+ local m20 = v1.X
138
+ local m21 = v1.Y
139
+ local m22 = v1.Z
140
+
141
+ local n8 = m00 + m11 + m22
142
+
143
+ if n8 > 0 then
144
+ local n = math.sqrt(n8 + 1)
145
+
146
+ return Quaternion.new((m12 - m21) * n, (m20 - m02) * n, (m01 - m10) * n, n * 0.5)
147
+ end
148
+
149
+ if m00 >= m11 and m00 >= m22 then
150
+ local n7 = math.sqrt(((1 + m00) - m11) - m22)
151
+ local n4 = 0.5 / n7
152
+
153
+ return Quaternion.new(0.5 * n7, (m01 + m10) * n4, (m02 + m20) * n4, (m12 - m21) * n4)
154
+ end
155
+
156
+ if m11 > m22 then
157
+ local n6 = math.sqrt(((1 + m11) - m00) - m22)
158
+ local n3 = 0.5 / n6
159
+
160
+ return Quaternion.new((m10 + m01) * n3, 0.5 * n6, (m21 + m12) * n3, (m20 - m02) * n3)
161
+ end
162
+
163
+ local n5 = math.sqrt(((1 + m22) - m00) - m11)
164
+ local n2 = 0.5 / n5
165
+
166
+ return Quaternion.new((m20 + m02) * n2, (m21 + m12) * n2, 0.5 * n5, (m01 - m10) * n2)
167
+ end
168
+
169
+ --[=[
170
+ Constructs a Quaternion from the rotation components of the given `cframe`.
171
+
172
+ This method ortho-normalizes the CFrame value, so there is no need to do this yourself
173
+ before calling the function.
174
+
175
+ ```lua
176
+ -- Create a Quaternion representing the rotational CFrame of a part:
177
+ local quat = Quaternion.cframe(somePart.CFrame)
178
+ ```
179
+ ]=]
180
+ function Quaternion.cframe(cframe: CFrame): Quaternion
181
+ local _, _, _, m00, m01, m02, m10, m11, m12, m20, m21, m22 = cframe:Orthonormalize():GetComponents()
182
+
183
+ local x, y, z, w
184
+
185
+ local trace = m00 + m11 + m22
186
+ if trace > 0 then
187
+ local s = math.sqrt(trace + 1) * 2
188
+ x = (m21 - m12) / s
189
+ y = (m02 - m20) / s
190
+ z = (m10 - m01) / s
191
+ w = 0.25 * s
192
+ elseif m00 > m11 and m00 > m22 then
193
+ local s = math.sqrt(1 + m00 - m11 - m22) * 2
194
+ x = 0.25 * s
195
+ y = (m01 + m10) / s
196
+ z = (m02 + m20) / s
197
+ w = (m21 - m12) / s
198
+ elseif m11 > m22 then
199
+ local s = math.sqrt(1 + m11 - m00 - m22) * 2
200
+ x = (m01 + m10) / s
201
+ y = 0.25 * s
202
+ z = (m12 + m21) / s
203
+ w = (m02 - m20) / s
204
+ else
205
+ local s = math.sqrt(1 + m22 - m00 - m11) * 2
206
+ x = (m02 + m20) / s
207
+ y = (m12 + m21) / s
208
+ z = 0.25 * s
209
+ w = (m10 - m01) / s
210
+ end
211
+
212
+ return Quaternion.new(x, y, z, w)
213
+ end
214
+
215
+ --[=[
216
+ @method Dot
217
+ @within Quaternion
218
+ @param other Quaternion
219
+ @return number
220
+
221
+ Calculates the dot product between the two Quaternions.
222
+
223
+ ```lua
224
+ local dot = quatA:Dot(quatB)
225
+ ```
226
+ ]=]
227
+ function Quaternion.Dot(self: Quaternion, other: Quaternion): number
228
+ return self.X * other.X + self.Y * other.Y + self.Z * other.Z + self.W * other.W
229
+ end
230
+
231
+ --[=[
232
+ @method Slerp
233
+ @within Quaternion
234
+ @param other Quaternion
235
+ @param t number
236
+ @return Quaternion
237
+
238
+ Calculates a spherical interpolation between the two Quaternions. Parameter `t` represents
239
+ the percentage between the two rotations, from a range of `[0, 1]`.
240
+
241
+ Spherical interpolation is great for smoothing or animating between quaternions.
242
+
243
+ ```lua
244
+ local midWay = quatA:Slerp(quatB, 0.5)
245
+ ```
246
+ ]=]
247
+ function Quaternion.Slerp(a: Quaternion, b: Quaternion, t: number): Quaternion
248
+ local cosOmega = a:Dot(b)
249
+ local flip = false
250
+
251
+ if cosOmega < 0 then
252
+ flip = true
253
+ cosOmega = -cosOmega
254
+ end
255
+
256
+ local s1, s2
257
+
258
+ if cosOmega > (1 - EPSILON) then
259
+ s1 = 1 - t
260
+ s2 = if flip then -t else t
261
+ else
262
+ local omega = math.acos(cosOmega)
263
+ local invSinOmega = 1 / math.sin(omega)
264
+
265
+ s1 = math.sin((1 - t) * omega) * invSinOmega
266
+ s2 = if flip then -math.sin(t * omega) * invSinOmega else math.sin(t * omega) * invSinOmega
267
+ end
268
+
269
+ return Quaternion.new(s1 * a.X + s2 * b.X, s1 * a.Y + s2 * b.Y, s1 * a.Z + s2 * b.Z, s1 * a.W + s2 * b.W)
270
+ end
271
+
272
+ --[=[
273
+ @method Angle
274
+ @within Quaternion
275
+ @param other Quaternion
276
+ @return number
277
+
278
+ Calculates the angle (radians) between the two Quaternions.
279
+
280
+ ```lua
281
+ local angle = quatA:Angle(quatB)
282
+ ```
283
+ ]=]
284
+ function Quaternion.Angle(self: Quaternion, other: Quaternion): number
285
+ local dot = math.min(math.abs(self:Dot(other)), 1)
286
+ local angle = if dot > (1 - EPSILON) then 0 else math.acos(dot) * 2
287
+
288
+ return angle
289
+ end
290
+
291
+ --[=[
292
+ @method RotateTowards
293
+ @within Quaternion
294
+ @param other Quaternion
295
+ @param maxRadiansDelta number
296
+ @return Quaternion
297
+
298
+ Constructs a new Quaternion that rotates from this Quaternion to the `other` quaternion, with a maximum
299
+ rotation of `maxRadiansDelta`. Internally, this calls `Slerp`, but limits the movement to `maxRadiansDelta`.
300
+
301
+ ```lua
302
+ -- Rotate from quatA to quatB, but only by 10 degrees:
303
+ local q = quatA:RotateTowards(quatB, math.rad(10))
304
+ ```
305
+ ]=]
306
+ function Quaternion.RotateTowards(self: Quaternion, other: Quaternion, maxRadiansDelta: number): Quaternion
307
+ local angle = self:Angle(other)
308
+
309
+ if angle == 0 then
310
+ return self
311
+ end
312
+
313
+ local alpha = maxRadiansDelta / angle
314
+
315
+ return self:Slerp(other, alpha)
316
+ end
317
+
318
+ --[=[
319
+ @method ToCFrame
320
+ @within Quaternion
321
+ @param position Vector3?
322
+ @return CFrame
323
+
324
+ Constructs a CFrame value representing the Quaternion. An optional `position` Vector can be given to
325
+ represent the position of the CFrame in 3D space. This defaults to `Vector3.zero`.
326
+
327
+ ```lua
328
+ -- Construct a CFrame from the quaternion, where the position will be at the origin point:
329
+ local cf = quat:ToCFrame()
330
+
331
+ -- Construct a CFrame with a given position:
332
+ local cf = quat:ToCFrame(someVector3)
333
+
334
+ -- e.g., set a part's CFrame:
335
+ local part = workspace.Part
336
+ local quat = Quaternion.axisAngle(Vector3.yAxis, math.rad(45))
337
+ local cframe = quat:ToCFrame(part.Position) -- Construct CFrame with a positional component
338
+ part.CFrame = cframe
339
+ ```
340
+ ]=]
341
+ function Quaternion.ToCFrame(self: Quaternion, position: Vector3?): CFrame
342
+ local pos = if position == nil then Vector3.zero else position
343
+
344
+ return CFrame.new(pos.X, pos.Y, pos.Z, self.X, self.Y, self.Z, self.W)
345
+ end
346
+
347
+ --[=[
348
+ @method ToEulerAngles
349
+ @within Quaternion
350
+ @return Vector3
351
+
352
+ Calculates the Euler angles (radians) that represent the Quaternion.
353
+
354
+ ```lua
355
+ local euler = quat:ToEulerAngles()
356
+ print(euler.X, euler.Y, euler.Z)
357
+ ```
358
+ ]=]
359
+ function Quaternion.ToEulerAngles(self: Quaternion): Vector3
360
+ local test = self.X * self.Y + self.Z * self.W
361
+ if test > 0.49999 then
362
+ return Vector3.new(0, 2 * math.atan2(self.X, self.W), math.pi / 2)
363
+ elseif test < -0.49999 then
364
+ return Vector3.new(0, -2 * math.atan2(self.X, self.W), -math.pi / 2)
365
+ end
366
+
367
+ local sqx = self.X * self.X
368
+ local sqy = self.Y * self.Y
369
+ local sqz = self.Z * self.Z
370
+
371
+ return Vector3.new(
372
+ math.atan2(2 * self.X * self.W - 2 * self.Y * self.Z, 1 - 2 * sqx - 2 * sqz),
373
+ math.atan2(2 * self.Y * self.W - 2 * self.X * self.Z, 1 - 2 * sqy - 2 * sqz),
374
+ math.asin(2 * test)
375
+ )
376
+ end
377
+
378
+ --[=[
379
+ @method ToAxisAngle
380
+ @within Quaternion
381
+ @return (Vector3, number)
382
+
383
+ Calculates the axis and angle representing the Quaternion.
384
+
385
+ ```lua
386
+ local axis, angle = quat:ToAxisAngle()
387
+ ```
388
+ ]=]
389
+ function Quaternion.ToAxisAngle(self: Quaternion): (Vector3, number)
390
+ local scale = math.sqrt(self.X * self.X + self.Y * self.Y + self.Z * self.Z)
391
+
392
+ if math.abs(scale) < EPSILON or self.W > 1 or self.W < -1 then
393
+ return Vector3.yAxis, 0
394
+ end
395
+
396
+ local invScale = 1 / scale
397
+
398
+ local axis = Vector3.new(self.X * invScale, self.Y * invScale, self.Z * invScale)
399
+ local angle = 2 * math.acos(self.W)
400
+
401
+ return axis, angle
402
+ end
403
+
404
+ --[=[
405
+ @method Inverse
406
+ @within Quaternion
407
+ @return Quaternion
408
+
409
+ Returns the inverse of the Quaternion.
410
+
411
+ ```lua
412
+ local quatInverse = quat:Inverse()
413
+ ```
414
+ ]=]
415
+ function Quaternion.Inverse(self: Quaternion): Quaternion
416
+ local dot = self:Dot(self)
417
+ local invNormal = 1 / dot
418
+ return Quaternion.new(-self.X * invNormal, -self.Y * invNormal, -self.Z * invNormal, self.W * invNormal)
419
+ end
420
+
421
+ --[=[
422
+ @method Conjugate
423
+ @within Quaternion
424
+ @return Quaternion
425
+
426
+ Returns the conjugate of the Quaternion. This is equal to `Quaternion.new(-X, -Y, -Z, W)`.
427
+
428
+ ```lua
429
+ local quatConjugate = quat:Conjugate()
430
+ ```
431
+ ]=]
432
+ function Quaternion.Conjugate(self: Quaternion): Quaternion
433
+ return Quaternion.new(-self.X, -self.Y, -self.Z, self.W)
434
+ end
435
+
436
+ --[=[
437
+ @method Normalize
438
+ @within Quaternion
439
+ @return Quaternion
440
+
441
+ Returns the normalized representation of the Quaternion.
442
+
443
+ ```lua
444
+ local quatNormalized = quat:Normalize()
445
+ ```
446
+ ]=]
447
+ function Quaternion.Normalize(self: Quaternion): Quaternion
448
+ local magnitude = self:Magnitude()
449
+
450
+ if magnitude < EPSILON then
451
+ return Quaternion.identity
452
+ end
453
+
454
+ return Quaternion.new(self.X / magnitude, self.Y / magnitude, self.Z / magnitude, self.W / magnitude)
455
+ end
456
+
457
+ --[=[
458
+ @method Magnitude
459
+ @within Quaternion
460
+ @return number
461
+
462
+ Calculates the magnitude of the Quaternion.
463
+
464
+ ```lua
465
+ local magnitude = quat:Magnitude()
466
+ ```
467
+ ]=]
468
+ function Quaternion.Magnitude(self: Quaternion): number
469
+ return math.sqrt(self:Dot(self))
470
+ end
471
+
472
+ --[=[
473
+ @method SqrMagnitude
474
+ @within Quaternion
475
+ @return number
476
+
477
+ Calculates the square magnitude of the Quaternion.
478
+
479
+ ```lua
480
+ local squareMagnitude = quat:Magnitude()
481
+ ```
482
+ ]=]
483
+ function Quaternion.SqrMagnitude(self: Quaternion): number
484
+ return self:Dot(self)
485
+ end
486
+
487
+ function Quaternion._MulVector3(self: Quaternion, other: Vector3): Vector3
488
+ local x = self.X * 2
489
+ local y = self.Y * 2
490
+ local z = self.Z * 2
491
+
492
+ local xx = self.X * x
493
+ local yy = self.Y * y
494
+ local zz = self.Z * z
495
+ local xy = self.X * y
496
+ local xz = self.X * z
497
+ local yz = self.Y * z
498
+ local wx = self.W * x
499
+ local wy = self.W * y
500
+ local wz = self.W * z
501
+
502
+ return Vector3.new(
503
+ (1 - (yy + zz)) * other.X + (xy - wz) * other.Y + (xz + wy) * other.Z,
504
+ (xy + wz) * other.X + (1 - (xx + zz)) * other.Y + (yz - wx) * other.Z,
505
+ (xz - wy) * other.X + (yz + wx) * other.Y + (1 - (xx + yy)) * other.Z
506
+ )
507
+ end
508
+
509
+ function Quaternion._MulQuaternion(self: Quaternion, other: Quaternion): Quaternion
510
+ return Quaternion.new(
511
+ self.W * other.X + self.X * other.W + self.Y * other.Z - self.Z * other.Y,
512
+ self.W * other.Y + self.Y * other.W + self.Z * other.X - self.X * other.Z,
513
+ self.W * other.Z + self.Z * other.W + self.X * other.Y - self.Y * other.X,
514
+ self.W * other.W - self.X * other.X - self.Y * other.Y - self.Z * other.Z
515
+ )
516
+ end
517
+
518
+ --[=[
519
+ Multiplication metamethod. A Quaternion can be multiplied with another Quaternion or
520
+ a Vector3.
521
+
522
+ ```lua
523
+ local quat = quatA * quatB
524
+ local vec = quatA * vecA
525
+ ```
526
+ ]=]
527
+ function Quaternion.__mul(self: Quaternion, other: Quaternion | Vector3): Quaternion | Vector3
528
+ local t = typeof(other)
529
+ if t == "Vector3" then
530
+ return Quaternion._MulVector3(self, other :: any)
531
+ elseif t == "table" and getmetatable(other :: any) == Quaternion then
532
+ return Quaternion._MulQuaternion(self, (other :: any) :: Quaternion)
533
+ else
534
+ error(`cannot multiply quaternion with type {t}`, 2)
535
+ end
536
+ end
537
+
538
+ function Quaternion.__unm(self: Quaternion): Quaternion
539
+ return Quaternion.new(-self.X, -self.Y, -self.Z, -self.W)
540
+ end
541
+
542
+ function Quaternion.__eq(self: Quaternion, other: Quaternion): boolean
543
+ return self.X == other.X and self.Y == other.Y and self.Z == other.Z and self.W == other.W
544
+ end
545
+
546
+ function Quaternion.__tostring(self: Quaternion): string
547
+ return `{self.X}, {self.Y}, {self.Z}, {self.W}`
548
+ end
549
+
550
+ -- Kind of support roblox-ts multiplication (not really, but follows the same pattern):
551
+ Quaternion.mul = Quaternion.__mul
552
+
553
+ --[=[
554
+ @prop identity Quaternion
555
+ @readonly
556
+ @within Quaternion
557
+
558
+ Identity Quaternion. Equal to `Quaternion.new(0, 0, 0, 1)`.
559
+ ]=]
560
+ Quaternion.identity = Quaternion.new(0, 0, 0, 1)
561
+
562
+ return {
563
+ new = Quaternion.new,
564
+ euler = Quaternion.euler,
565
+ axisAngle = Quaternion.axisAngle,
566
+ lookRotation = Quaternion.lookRotation,
567
+ cframe = Quaternion.cframe,
568
+
569
+ identity = Quaternion.identity,
570
+ }