roblox-opencode 1.0.0 → 1.0.2
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.
- package/README.md +112 -122
- package/commands/setup-game.md +108 -108
- package/commands/sync-check.md +53 -53
- package/core/roblox-core.md +93 -93
- package/dist/server.js +189 -167
- package/package.json +35 -35
- package/skills/roblox-analytics/SKILL.md +277 -277
- package/skills/roblox-analytics/references/event-batcher.luau +75 -75
- package/skills/roblox-animation-vfx/SKILL.md +1325 -1325
- package/skills/roblox-architecture/SKILL.md +877 -863
- package/skills/roblox-architecture/references/combat-systems.md +1381 -1381
- package/skills/roblox-code-review/SKILL.md +686 -686
- package/skills/roblox-data/SKILL.md +889 -889
- package/skills/roblox-data/references/inventory-systems.md +1729 -1729
- package/skills/roblox-debug/SKILL.md +98 -98
- package/skills/roblox-gui/SKILL.md +1103 -1103
- package/skills/roblox-gui-fusion/SKILL.md +150 -150
- package/skills/roblox-gui-fusion/references/inventory.luau +427 -427
- package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -579
- package/skills/roblox-gui-fusion/references/shop.luau +411 -411
- package/skills/roblox-luau-mastery/SKILL.md +1618 -1519
- package/skills/roblox-monetization/SKILL.md +1084 -1084
- package/skills/roblox-monetization/references/process-receipt.luau +131 -131
- package/skills/roblox-networking/SKILL.md +669 -669
- package/skills/roblox-networking/references/remote-validator.luau +193 -193
- package/skills/roblox-publish-checklist/SKILL.md +127 -127
- package/skills/roblox-runtime/SKILL.md +753 -753
- package/skills/roblox-sharp-edges/SKILL.md +294 -294
- package/skills/roblox-sync/SKILL.md +126 -126
- package/skills/roblox-testing/SKILL.md +943 -943
- package/skills/roblox-tooling/SKILL.md +149 -149
- package/vendor/LICENSES/ProfileStore-LICENSE +201 -201
- package/vendor/LICENSES/RbxUtil-LICENSE +7 -7
- package/vendor/LICENSES/promise-LICENSE +20 -20
- package/vendor/LICENSES/t-LICENSE +21 -21
- package/vendor/LICENSES/testez-LICENSE +200 -200
- package/vendor/README.md +83 -83
- package/vendor/fusion/Animation/ExternalTime.luau +83 -83
- package/vendor/fusion/Animation/Spring.luau +321 -321
- package/vendor/fusion/Animation/Stopwatch.luau +127 -127
- package/vendor/fusion/Animation/Tween.luau +187 -187
- package/vendor/fusion/Animation/getTweenDuration.luau +27 -27
- package/vendor/fusion/Animation/getTweenRatio.luau +47 -47
- package/vendor/fusion/Animation/lerpType.luau +163 -163
- package/vendor/fusion/Animation/packType.luau +99 -99
- package/vendor/fusion/Animation/springCoefficients.luau +80 -80
- package/vendor/fusion/Animation/unpackType.luau +102 -102
- package/vendor/fusion/Colour/Oklab.luau +70 -70
- package/vendor/fusion/Colour/sRGB.luau +54 -54
- package/vendor/fusion/External.luau +167 -167
- package/vendor/fusion/ExternalDebug.luau +69 -69
- package/vendor/fusion/Graph/Observer.luau +113 -113
- package/vendor/fusion/Graph/castToGraph.luau +28 -28
- package/vendor/fusion/Graph/change.luau +80 -80
- package/vendor/fusion/Graph/depend.luau +32 -32
- package/vendor/fusion/Graph/evaluate.luau +55 -55
- package/vendor/fusion/Instances/Attribute.luau +57 -57
- package/vendor/fusion/Instances/AttributeChange.luau +46 -46
- package/vendor/fusion/Instances/AttributeOut.luau +63 -63
- package/vendor/fusion/Instances/Child.luau +21 -21
- package/vendor/fusion/Instances/Children.luau +147 -147
- package/vendor/fusion/Instances/Hydrate.luau +32 -32
- package/vendor/fusion/Instances/New.luau +52 -52
- package/vendor/fusion/Instances/OnChange.luau +49 -49
- package/vendor/fusion/Instances/OnEvent.luau +53 -53
- package/vendor/fusion/Instances/Out.luau +69 -69
- package/vendor/fusion/Instances/applyInstanceProps.luau +148 -148
- package/vendor/fusion/Instances/defaultProps.luau +194 -194
- package/vendor/fusion/LICENSE +21 -21
- package/vendor/fusion/Logging/formatError.luau +48 -48
- package/vendor/fusion/Logging/messages.luau +51 -51
- package/vendor/fusion/Logging/parseError.luau +24 -24
- package/vendor/fusion/Memory/checkLifetime.luau +133 -133
- package/vendor/fusion/Memory/deriveScope.luau +23 -23
- package/vendor/fusion/Memory/deriveScopeImpl.luau +44 -44
- package/vendor/fusion/Memory/doCleanup.luau +78 -78
- package/vendor/fusion/Memory/innerScope.luau +33 -33
- package/vendor/fusion/Memory/legacyCleanup.luau +17 -17
- package/vendor/fusion/Memory/needsDestruction.luau +16 -16
- package/vendor/fusion/Memory/poisonScope.luau +33 -33
- package/vendor/fusion/Memory/scopePool.luau +54 -54
- package/vendor/fusion/Memory/scoped.luau +26 -26
- package/vendor/fusion/Memory/whichLivesLonger.luau +74 -74
- package/vendor/fusion/RobloxExternal.luau +97 -97
- package/vendor/fusion/State/Computed.luau +138 -138
- package/vendor/fusion/State/For/Disassembly.luau +210 -210
- package/vendor/fusion/State/For/ForTypes.luau +30 -30
- package/vendor/fusion/State/For/init.luau +109 -109
- package/vendor/fusion/State/ForKeys.luau +93 -93
- package/vendor/fusion/State/ForPairs.luau +96 -96
- package/vendor/fusion/State/ForValues.luau +93 -93
- package/vendor/fusion/State/Value.luau +87 -87
- package/vendor/fusion/State/castToState.luau +25 -25
- package/vendor/fusion/State/peek.luau +30 -30
- package/vendor/fusion/Types.luau +314 -314
- package/vendor/fusion/Utility/Contextual.luau +90 -90
- package/vendor/fusion/Utility/Safe.luau +22 -22
- package/vendor/fusion/Utility/isSimilar.luau +29 -29
- package/vendor/fusion/Utility/merge.luau +35 -35
- package/vendor/fusion/Utility/nameOf.luau +34 -34
- package/vendor/fusion/Utility/never.luau +13 -13
- package/vendor/fusion/Utility/nicknames.luau +10 -10
- package/vendor/fusion/Utility/xtypeof.luau +26 -26
- package/vendor/fusion/init.luau +82 -82
- package/vendor/profilestore/init.luau +2242 -2242
- package/vendor/promise/init.luau +1982 -1982
- package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -25
- package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -228
- package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -269
- package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -223
- package/vendor/rbxutil/buffer-util/Types.luau +60 -60
- package/vendor/rbxutil/buffer-util/index.d.ts +153 -153
- package/vendor/rbxutil/buffer-util/init.luau +41 -41
- package/vendor/rbxutil/buffer-util/package.json +16 -16
- package/vendor/rbxutil/buffer-util/wally.toml +9 -9
- package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -232
- package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -156
- package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -109
- package/vendor/rbxutil/comm/Client/init.luau +135 -135
- package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -295
- package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -211
- package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -211
- package/vendor/rbxutil/comm/Server/init.luau +140 -140
- package/vendor/rbxutil/comm/Types.luau +18 -18
- package/vendor/rbxutil/comm/Util.luau +27 -27
- package/vendor/rbxutil/comm/init.luau +35 -35
- package/vendor/rbxutil/comm/wally.toml +13 -13
- package/vendor/rbxutil/component/init.luau +759 -759
- package/vendor/rbxutil/component/init.test.luau +311 -311
- package/vendor/rbxutil/component/wally.toml +14 -14
- package/vendor/rbxutil/concur/init.luau +542 -542
- package/vendor/rbxutil/concur/init.test.luau +364 -364
- package/vendor/rbxutil/concur/wally.toml +8 -8
- package/vendor/rbxutil/enum-list/init.luau +101 -101
- package/vendor/rbxutil/enum-list/init.test.luau +91 -91
- package/vendor/rbxutil/enum-list/wally.toml +8 -8
- package/vendor/rbxutil/find/index.d.ts +20 -20
- package/vendor/rbxutil/find/init.luau +44 -44
- package/vendor/rbxutil/find/package.json +17 -17
- package/vendor/rbxutil/find/wally.toml +8 -8
- package/vendor/rbxutil/input/Gamepad.luau +559 -559
- package/vendor/rbxutil/input/Keyboard.luau +124 -124
- package/vendor/rbxutil/input/Mouse.luau +278 -278
- package/vendor/rbxutil/input/PreferredInput.luau +91 -91
- package/vendor/rbxutil/input/Touch.luau +120 -120
- package/vendor/rbxutil/input/init.luau +33 -33
- package/vendor/rbxutil/input/wally.toml +12 -12
- package/vendor/rbxutil/loader/index.d.ts +15 -15
- package/vendor/rbxutil/loader/init.luau +137 -137
- package/vendor/rbxutil/loader/wally.toml +8 -8
- package/vendor/rbxutil/log/index.d.ts +38 -38
- package/vendor/rbxutil/log/init.luau +746 -746
- package/vendor/rbxutil/log/wally.toml +8 -8
- package/vendor/rbxutil/net/init.luau +190 -190
- package/vendor/rbxutil/net/wally.toml +8 -8
- package/vendor/rbxutil/option/index.d.ts +44 -44
- package/vendor/rbxutil/option/init.luau +489 -489
- package/vendor/rbxutil/option/init.test.luau +342 -342
- package/vendor/rbxutil/option/wally.toml +8 -8
- package/vendor/rbxutil/pid/index.d.ts +53 -53
- package/vendor/rbxutil/pid/init.luau +195 -195
- package/vendor/rbxutil/pid/package.json +16 -16
- package/vendor/rbxutil/pid/wally.toml +9 -9
- package/vendor/rbxutil/quaternion/index.d.ts +117 -117
- package/vendor/rbxutil/quaternion/init.luau +570 -570
- package/vendor/rbxutil/quaternion/package.json +16 -16
- package/vendor/rbxutil/quaternion/wally.toml +9 -9
- package/vendor/rbxutil/query/index.d.ts +43 -43
- package/vendor/rbxutil/query/init.luau +117 -117
- package/vendor/rbxutil/query/package.json +18 -18
- package/vendor/rbxutil/query/wally.toml +9 -9
- package/vendor/rbxutil/sequent/index.d.ts +28 -28
- package/vendor/rbxutil/sequent/init.luau +340 -340
- package/vendor/rbxutil/sequent/package.json +16 -16
- package/vendor/rbxutil/sequent/wally.toml +9 -9
- package/vendor/rbxutil/ser/init.luau +175 -175
- package/vendor/rbxutil/ser/init.test.luau +50 -50
- package/vendor/rbxutil/ser/wally.toml +11 -11
- package/vendor/rbxutil/shake/index.d.ts +36 -36
- package/vendor/rbxutil/shake/init.luau +532 -532
- package/vendor/rbxutil/shake/init.test.luau +267 -267
- package/vendor/rbxutil/shake/package.json +16 -16
- package/vendor/rbxutil/shake/wally.toml +9 -9
- package/vendor/rbxutil/signal/index.d.ts +100 -100
- package/vendor/rbxutil/signal/init.luau +432 -432
- package/vendor/rbxutil/signal/init.test.luau +190 -190
- package/vendor/rbxutil/signal/package.json +17 -17
- package/vendor/rbxutil/signal/wally.toml +9 -9
- package/vendor/rbxutil/silo/TableWatcher.luau +65 -65
- package/vendor/rbxutil/silo/Util.luau +55 -55
- package/vendor/rbxutil/silo/init.luau +338 -338
- package/vendor/rbxutil/silo/init.test.luau +215 -215
- package/vendor/rbxutil/silo/wally.toml +8 -8
- package/vendor/rbxutil/spring/index.d.ts +40 -40
- package/vendor/rbxutil/spring/init.luau +97 -97
- package/vendor/rbxutil/spring/package.json +17 -17
- package/vendor/rbxutil/spring/wally.toml +8 -8
- package/vendor/rbxutil/stream/index.d.ts +88 -88
- package/vendor/rbxutil/stream/init.luau +597 -597
- package/vendor/rbxutil/stream/package.json +18 -18
- package/vendor/rbxutil/stream/wally.toml +9 -9
- package/vendor/rbxutil/streamable/Streamable.luau +202 -202
- package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -80
- package/vendor/rbxutil/streamable/init.luau +8 -8
- package/vendor/rbxutil/streamable/wally.toml +12 -12
- package/vendor/rbxutil/symbol/init.luau +56 -56
- package/vendor/rbxutil/symbol/init.test.luau +37 -37
- package/vendor/rbxutil/symbol/wally.toml +8 -8
- package/vendor/rbxutil/table-util/init.luau +938 -938
- package/vendor/rbxutil/table-util/init.test.luau +439 -439
- package/vendor/rbxutil/task-queue/index.d.ts +27 -27
- package/vendor/rbxutil/task-queue/init.luau +97 -97
- package/vendor/rbxutil/task-queue/wally.toml +8 -8
- package/vendor/rbxutil/timer/index.d.ts +81 -81
- package/vendor/rbxutil/timer/init.luau +249 -249
- package/vendor/rbxutil/timer/init.test.luau +73 -73
- package/vendor/rbxutil/timer/wally.toml +11 -11
- package/vendor/rbxutil/tree/index.d.ts +15 -15
- package/vendor/rbxutil/tree/init.luau +137 -137
- package/vendor/rbxutil/tree/wally.toml +8 -8
- package/vendor/rbxutil/trove/index.d.ts +46 -46
- package/vendor/rbxutil/trove/init.luau +787 -787
- package/vendor/rbxutil/trove/init.test.luau +203 -203
- package/vendor/rbxutil/trove/wally.toml +8 -8
- package/vendor/rbxutil/typed-remote/init.luau +196 -196
- package/vendor/rbxutil/typed-remote/wally.toml +8 -8
- package/vendor/rbxutil/wait-for/index.d.ts +17 -17
- package/vendor/rbxutil/wait-for/init.luau +257 -257
- package/vendor/rbxutil/wait-for/init.test.luau +182 -182
- package/vendor/rbxutil/wait-for/wally.toml +11 -11
- package/vendor/t/t.lua +1350 -1350
- package/vendor/testez/Context.lua +26 -26
- package/vendor/testez/Expectation.lua +311 -311
- package/vendor/testez/ExpectationContext.lua +38 -38
- package/vendor/testez/LifecycleHooks.lua +89 -89
- package/vendor/testez/Reporters/TeamCityReporter.lua +101 -101
- package/vendor/testez/Reporters/TextReporter.lua +105 -105
- package/vendor/testez/Reporters/TextReporterQuiet.lua +96 -96
- package/vendor/testez/TestBootstrap.lua +146 -146
- package/vendor/testez/TestEnum.lua +27 -27
- package/vendor/testez/TestPlan.lua +304 -304
- package/vendor/testez/TestPlanner.lua +39 -39
- package/vendor/testez/TestResults.lua +111 -111
- package/vendor/testez/TestRunner.lua +188 -188
- package/vendor/testez/TestSession.lua +243 -243
- 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
|
+
}
|