@quenty/geometryutils 6.8.1 → 6.8.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/CHANGELOG.md +11 -0
- package/package.json +4 -4
- package/src/Shared/CameraPyramidUtils.lua +56 -40
- package/src/Shared/CircleUtils.lua +2 -1
- package/src/Shared/Line.lua +16 -13
- package/src/Shared/OrthogonalUtils.lua +6 -5
- package/src/Shared/PlaneUtils.lua +4 -2
- package/src/Shared/ScaleModelUtils.lua +12 -13
- package/src/Shared/SphereUtils.lua +4 -3
- package/src/Shared/SurfaceUtils.lua +4 -3
- package/src/Shared/SwingTwistUtils.lua +7 -6
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [6.8.2](https://github.com/Quenty/NevermoreEngine/compare/@quenty/geometryutils@6.8.1...@quenty/geometryutils@6.8.2) (2025-04-05)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* Add types to packages ([2374fb2](https://github.com/Quenty/NevermoreEngine/commit/2374fb2b043cfbe0e9b507b3316eec46a4e353a0))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
## [6.8.1](https://github.com/Quenty/NevermoreEngine/compare/@quenty/geometryutils@6.8.0...@quenty/geometryutils@6.8.1) (2025-03-21)
|
|
7
18
|
|
|
8
19
|
**Note:** Version bump only for package @quenty/geometryutils
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/geometryutils",
|
|
3
|
-
"version": "6.8.
|
|
3
|
+
"version": "6.8.2",
|
|
4
4
|
"description": "Utility functions involving 3D and 2D geometry",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@quenty/draw": "^7.8.
|
|
32
|
-
"@quenty/loader": "^10.8.
|
|
31
|
+
"@quenty/draw": "^7.8.2",
|
|
32
|
+
"@quenty/loader": "^10.8.1"
|
|
33
33
|
},
|
|
34
|
-
"gitHead": "
|
|
34
|
+
"gitHead": "78c3ac0ab08dd18085b6e6e6e4f745e76ed99f68"
|
|
35
35
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
@class CameraPyramidUtils
|
|
3
4
|
]=]
|
|
@@ -9,7 +10,10 @@ local Draw = require("Draw")
|
|
|
9
10
|
|
|
10
11
|
local CameraPyramidUtils = {}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
type InBoundIntersection = {
|
|
14
|
+
point: Vector3,
|
|
15
|
+
dist: number,
|
|
16
|
+
}
|
|
13
17
|
|
|
14
18
|
--[=[
|
|
15
19
|
Treating the camera like a pyramid, compute points on the screen that the ray intersects with the
|
|
@@ -25,23 +29,28 @@ local CameraPyramidUtils = {}
|
|
|
25
29
|
@return Vector3? -- Screen point1
|
|
26
30
|
@return Vector3? -- Screen point2
|
|
27
31
|
]=]
|
|
28
|
-
function CameraPyramidUtils.rayIntersection(
|
|
32
|
+
function CameraPyramidUtils.rayIntersection(
|
|
33
|
+
camera: Camera,
|
|
34
|
+
rayOrigin: Vector3,
|
|
35
|
+
unitRayDirection: Vector3,
|
|
36
|
+
debugMaid: any?
|
|
37
|
+
): (Vector3?, Vector3?)
|
|
29
38
|
assert(typeof(rayOrigin) == "Vector3", "Bad rayOrigin")
|
|
30
39
|
assert(typeof(unitRayDirection) == "Vector3", "Bad unitRayDirection")
|
|
31
40
|
|
|
32
|
-
unitRayDirection = unitRayDirection.
|
|
41
|
+
unitRayDirection = unitRayDirection.Unit
|
|
33
42
|
local camCFrame = camera.CFrame
|
|
34
43
|
local viewportSize = camera.ViewportSize
|
|
35
|
-
if viewportSize.
|
|
44
|
+
if viewportSize.X <= 0 or viewportSize.Y <= 0 then
|
|
36
45
|
return nil, nil
|
|
37
46
|
end
|
|
38
47
|
|
|
39
|
-
local aspectRatio = viewportSize.
|
|
40
|
-
local halfVerticalFov = math.rad(camera.FieldOfView/2)
|
|
41
|
-
local halfHorizontalFov = math.atan(math.tan(halfVerticalFov)*aspectRatio)
|
|
48
|
+
local aspectRatio = viewportSize.X / viewportSize.Y
|
|
49
|
+
local halfVerticalFov = math.rad(camera.FieldOfView / 2)
|
|
50
|
+
local halfHorizontalFov = math.atan(math.tan(halfVerticalFov) * aspectRatio)
|
|
42
51
|
|
|
43
52
|
-- Construct pyramid with normals facing out
|
|
44
|
-
local origin = camCFrame.
|
|
53
|
+
local origin = camCFrame.Position
|
|
45
54
|
local cframeTop = (camCFrame * CFrame.Angles(halfVerticalFov, 0, 0))
|
|
46
55
|
local cframeBottom = (camCFrame * CFrame.Angles(-halfVerticalFov, 0, 0))
|
|
47
56
|
local cframeLeft = (camCFrame * CFrame.Angles(0, halfHorizontalFov, 0))
|
|
@@ -62,42 +71,44 @@ function CameraPyramidUtils.rayIntersection(camera, rayOrigin, unitRayDirection,
|
|
|
62
71
|
local leftInBounds = CameraPyramidUtils._isInBounds(camCFrame, intersectionLeft, halfVerticalFov, false)
|
|
63
72
|
local rightInBounds = CameraPyramidUtils._isInBounds(camCFrame, intersectionRight, halfVerticalFov, false)
|
|
64
73
|
|
|
65
|
-
local inBoundsIntersections = {}
|
|
66
|
-
if topInBounds then
|
|
74
|
+
local inBoundsIntersections: { InBoundIntersection } = {}
|
|
75
|
+
if topInBounds and intersectionTop and distTop then
|
|
67
76
|
table.insert(inBoundsIntersections, {
|
|
68
|
-
point = intersectionTop
|
|
69
|
-
dist = distTop
|
|
77
|
+
point = intersectionTop,
|
|
78
|
+
dist = distTop,
|
|
70
79
|
})
|
|
71
80
|
end
|
|
72
|
-
if bottomInBounds then
|
|
81
|
+
if bottomInBounds and intersectionBottom and distBottom then
|
|
73
82
|
table.insert(inBoundsIntersections, {
|
|
74
|
-
point = intersectionBottom
|
|
75
|
-
dist = distBottom
|
|
83
|
+
point = intersectionBottom,
|
|
84
|
+
dist = distBottom,
|
|
76
85
|
})
|
|
77
86
|
end
|
|
78
|
-
if leftInBounds then
|
|
87
|
+
if leftInBounds and intersectionLeft and distLeft then
|
|
79
88
|
table.insert(inBoundsIntersections, {
|
|
80
|
-
point = intersectionLeft
|
|
81
|
-
dist = distLeft
|
|
89
|
+
point = intersectionLeft,
|
|
90
|
+
dist = distLeft,
|
|
82
91
|
})
|
|
83
92
|
end
|
|
84
|
-
if rightInBounds then
|
|
93
|
+
if rightInBounds and intersectionRight and distRight then
|
|
85
94
|
table.insert(inBoundsIntersections, {
|
|
86
|
-
point = intersectionRight
|
|
87
|
-
dist = distRight
|
|
95
|
+
point = intersectionRight,
|
|
96
|
+
dist = distRight,
|
|
88
97
|
})
|
|
89
98
|
end
|
|
90
99
|
|
|
91
100
|
if debugMaid then
|
|
92
101
|
debugMaid._top = CameraPyramidUtils._drawIntersection(camera, unitRayDirection, intersectionTop, topInBounds)
|
|
93
|
-
debugMaid._bottom =
|
|
102
|
+
debugMaid._bottom =
|
|
103
|
+
CameraPyramidUtils._drawIntersection(camera, unitRayDirection, intersectionBottom, bottomInBounds)
|
|
94
104
|
debugMaid._left = CameraPyramidUtils._drawIntersection(camera, unitRayDirection, intersectionLeft, leftInBounds)
|
|
95
|
-
debugMaid._right =
|
|
105
|
+
debugMaid._right =
|
|
106
|
+
CameraPyramidUtils._drawIntersection(camera, unitRayDirection, intersectionRight, rightInBounds)
|
|
96
107
|
end
|
|
97
108
|
|
|
98
109
|
if #inBoundsIntersections == 0 then
|
|
99
110
|
return nil, nil
|
|
100
|
-
|
|
111
|
+
elseif #inBoundsIntersections == 1 then
|
|
101
112
|
-- Happens when the other point fades off into the distance on this one screen such that it never ends
|
|
102
113
|
local data = inBoundsIntersections[1]
|
|
103
114
|
local intersection = data.point
|
|
@@ -136,44 +147,49 @@ function CameraPyramidUtils.rayIntersection(camera, rayOrigin, unitRayDirection,
|
|
|
136
147
|
end
|
|
137
148
|
end
|
|
138
149
|
|
|
139
|
-
function CameraPyramidUtils._drawIntersection(
|
|
140
|
-
|
|
150
|
+
function CameraPyramidUtils._drawIntersection(
|
|
151
|
+
camera: Camera,
|
|
152
|
+
unitRayDirection: Vector3,
|
|
153
|
+
intersection: Vector3?,
|
|
154
|
+
inBounds: boolean
|
|
155
|
+
): Instance?
|
|
156
|
+
if not inBounds or not intersection then
|
|
141
157
|
return nil
|
|
142
158
|
end
|
|
143
159
|
|
|
144
|
-
local halfVerticalFov = math.rad(camera.FieldOfView/2)
|
|
160
|
+
local halfVerticalFov = math.rad(camera.FieldOfView / 2)
|
|
145
161
|
local viewportSize = camera.ViewportSize
|
|
146
162
|
local PIXELS_DIAMETER = 40
|
|
147
|
-
local PIXELS_OFFSET = 5 + PIXELS_DIAMETER/2
|
|
163
|
+
local PIXELS_OFFSET = 5 + PIXELS_DIAMETER / 2
|
|
148
164
|
|
|
149
165
|
local position = camera:WorldToViewportPoint(intersection)
|
|
150
|
-
local dist = position.
|
|
151
|
-
local worldHeight = 2*math.tan(halfVerticalFov)*dist
|
|
152
|
-
local scale = worldHeight/viewportSize.
|
|
166
|
+
local dist = position.Z
|
|
167
|
+
local worldHeight = 2 * math.tan(halfVerticalFov) * dist
|
|
168
|
+
local scale = worldHeight / viewportSize.Y
|
|
153
169
|
|
|
154
|
-
local firstPoint = intersection + PIXELS_OFFSET*unitRayDirection*scale
|
|
155
|
-
local secondPoint = intersection - PIXELS_OFFSET*unitRayDirection*scale
|
|
170
|
+
local firstPoint = intersection + PIXELS_OFFSET * unitRayDirection * scale
|
|
171
|
+
local secondPoint = intersection - PIXELS_OFFSET * unitRayDirection * scale
|
|
156
172
|
|
|
157
173
|
local _, onScreen1 = camera:WorldToViewportPoint(firstPoint)
|
|
158
174
|
local _, onScreen2 = camera:WorldToViewportPoint(secondPoint)
|
|
159
175
|
|
|
160
176
|
local color = Color3.new(1, 1, 0)
|
|
161
177
|
if onScreen1 then
|
|
162
|
-
return Draw.point(firstPoint, color, nil, PIXELS_DIAMETER*scale)
|
|
178
|
+
return Draw.point(firstPoint, color, nil, PIXELS_DIAMETER * scale)
|
|
163
179
|
elseif onScreen2 then
|
|
164
|
-
return Draw.point(secondPoint, color, nil, PIXELS_DIAMETER*scale)
|
|
180
|
+
return Draw.point(secondPoint, color, nil, PIXELS_DIAMETER * scale)
|
|
165
181
|
else
|
|
166
182
|
return nil
|
|
167
183
|
end
|
|
168
184
|
end
|
|
169
185
|
|
|
170
|
-
function CameraPyramidUtils._isInBounds(camCFrame, intersection
|
|
186
|
+
function CameraPyramidUtils._isInBounds(camCFrame: CFrame, intersection: Vector3?, halfFov: number, isVertical: boolean): boolean
|
|
171
187
|
if not intersection then
|
|
172
188
|
return false
|
|
173
189
|
end
|
|
174
190
|
|
|
175
|
-
local relative = camCFrame:
|
|
176
|
-
local dist = -relative.
|
|
191
|
+
local relative = camCFrame:PointToObjectSpace(intersection)
|
|
192
|
+
local dist = -relative.Z
|
|
177
193
|
|
|
178
194
|
if dist < 0 then
|
|
179
195
|
return false
|
|
@@ -182,9 +198,9 @@ function CameraPyramidUtils._isInBounds(camCFrame, intersection, halfFov, isVert
|
|
|
182
198
|
-- Discard the other information (we're projecting onto the flat camera plane)
|
|
183
199
|
local horizontalDist
|
|
184
200
|
if isVertical then
|
|
185
|
-
horizontalDist = math.abs(relative.
|
|
201
|
+
horizontalDist = math.abs(relative.X)
|
|
186
202
|
else
|
|
187
|
-
horizontalDist = math.abs(relative.
|
|
203
|
+
horizontalDist = math.abs(relative.Y)
|
|
188
204
|
end
|
|
189
205
|
|
|
190
206
|
local angle = math.atan2(horizontalDist, dist)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
@class CircleUtils
|
|
3
4
|
]=]
|
|
@@ -13,7 +14,7 @@ local CircleUtils = {}
|
|
|
13
14
|
@param circumference number
|
|
14
15
|
@return number
|
|
15
16
|
]=]
|
|
16
|
-
function CircleUtils.updatePositionToSmallestDistOnCircle(position, target, circumference)
|
|
17
|
+
function CircleUtils.updatePositionToSmallestDistOnCircle(position: number, target: number, circumference: number): number
|
|
17
18
|
assert(target >= 0 and target <= circumference, "Target must be between 0 and circumference")
|
|
18
19
|
|
|
19
20
|
if math.abs(position - target) <= circumference/2 then
|
package/src/Shared/Line.lua
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
@class Line
|
|
3
4
|
]=]
|
|
@@ -10,25 +11,27 @@ local Line = {}
|
|
|
10
11
|
@param r Vector3 -- Offset from a
|
|
11
12
|
@param b Vector3
|
|
12
13
|
@param s Vector3
|
|
14
|
+
@return Vector3
|
|
15
|
+
@return Vector3
|
|
13
16
|
]=]
|
|
14
|
-
function Line.intersection(a, r, b, s)
|
|
15
|
-
local q = a - b
|
|
17
|
+
function Line.intersection(a: Vector3, r: Vector3, b: Vector3, s: Vector3): (Vector3, Vector3)
|
|
18
|
+
local q = a - b
|
|
16
19
|
|
|
17
|
-
local dotqr = q:Dot(r)
|
|
18
|
-
local dotqs = q:Dot(s)
|
|
19
|
-
local dotrs = r:Dot(s)
|
|
20
|
-
local dotrr = r:Dot(r)
|
|
21
|
-
local dotss = s:Dot(s)
|
|
20
|
+
local dotqr = q:Dot(r) -- same as: r:Dot(q)
|
|
21
|
+
local dotqs = q:Dot(s) -- same as: s:Dot(q)
|
|
22
|
+
local dotrs = r:Dot(s) -- same as: s:Dot(r)
|
|
23
|
+
local dotrr = r:Dot(r)
|
|
24
|
+
local dotss = s:Dot(s)
|
|
22
25
|
|
|
23
|
-
local denom = dotrr * dotss - dotrs * dotrs
|
|
24
|
-
local numer = dotqs * dotrs - dotqr * dotss
|
|
26
|
+
local denom = dotrr * dotss - dotrs * dotrs
|
|
27
|
+
local numer = dotqs * dotrs - dotqr * dotss
|
|
25
28
|
|
|
26
|
-
local t = numer / denom
|
|
27
|
-
local u = (dotqs + t * dotrs) / dotss
|
|
29
|
+
local t = numer / denom
|
|
30
|
+
local u = (dotqs + t * dotrs) / dotss
|
|
28
31
|
|
|
29
32
|
-- return the two points on each line that make up the shortest line
|
|
30
|
-
local p0, p1 = a + t * r, b + u * s
|
|
31
|
-
return p0, p1
|
|
33
|
+
local p0, p1 = a + t * r, b + u * s
|
|
34
|
+
return p0, p1
|
|
32
35
|
end
|
|
33
36
|
|
|
34
37
|
return Line
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
Utilities involving orthogonal Vector3s
|
|
3
4
|
@class OrthogonalUtils
|
|
@@ -10,8 +11,8 @@ local OrthogonalUtils = {}
|
|
|
10
11
|
@param cframe CFrame
|
|
11
12
|
return { Vector3 }
|
|
12
13
|
]=]
|
|
13
|
-
function OrthogonalUtils.decomposeCFrameToVectors(cframe)
|
|
14
|
-
return
|
|
14
|
+
function OrthogonalUtils.decomposeCFrameToVectors(cframe: CFrame): { Vector3 }
|
|
15
|
+
return {
|
|
15
16
|
cframe.LookVector, -- front
|
|
16
17
|
-cframe.LookVector,
|
|
17
18
|
cframe.RightVector,
|
|
@@ -28,10 +29,10 @@ end
|
|
|
28
29
|
@param unitVector Vector3
|
|
29
30
|
return Vector3?
|
|
30
31
|
]=]
|
|
31
|
-
function OrthogonalUtils.getClosestVector(options, unitVector)
|
|
32
|
+
function OrthogonalUtils.getClosestVector(options: { Vector3 }, unitVector: Vector3): Vector3?
|
|
32
33
|
local best = nil
|
|
33
34
|
local bestAngle = -math.huge
|
|
34
|
-
for _, option in
|
|
35
|
+
for _, option in options do
|
|
35
36
|
local dotAngle = option:Dot(unitVector)
|
|
36
37
|
if dotAngle > bestAngle then
|
|
37
38
|
bestAngle = dotAngle
|
|
@@ -49,7 +50,7 @@ end
|
|
|
49
50
|
@param snapToCFrame CFrame
|
|
50
51
|
return CFrame
|
|
51
52
|
]=]
|
|
52
|
-
function OrthogonalUtils.snapCFrameTo(cframe, snapToCFrame)
|
|
53
|
+
function OrthogonalUtils.snapCFrameTo(cframe: CFrame, snapToCFrame: CFrame): CFrame
|
|
53
54
|
local options = OrthogonalUtils.decomposeCFrameToVectors(snapToCFrame)
|
|
54
55
|
local rightVector = OrthogonalUtils.getClosestVector(options, cframe.RightVector)
|
|
55
56
|
local upVector = OrthogonalUtils.getClosestVector(options, cframe.UpVector)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
Utility functions involving planes!
|
|
3
4
|
@class PlaneUtils
|
|
@@ -16,13 +17,14 @@ local PlaneUtils = {}
|
|
|
16
17
|
@param rayOrigin Vector3
|
|
17
18
|
@param unitRayDirection Vector3
|
|
18
19
|
@return Vector3? -- Intersection point
|
|
20
|
+
@return number? -- Distance to intersection point
|
|
19
21
|
]=]
|
|
20
|
-
function PlaneUtils.rayIntersection(origin, normal, rayOrigin, unitRayDirection)
|
|
22
|
+
function PlaneUtils.rayIntersection(origin: Vector3, normal: Vector3, rayOrigin: Vector3, unitRayDirection: Vector3): (Vector3?, number?)
|
|
21
23
|
local rpoint = rayOrigin - origin
|
|
22
24
|
local dot = unitRayDirection:Dot(normal)
|
|
23
25
|
if dot == 0 then
|
|
24
26
|
-- Parallel
|
|
25
|
-
return nil
|
|
27
|
+
return nil, nil
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
local t = -rpoint:Dot(normal) / dot
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
Utility methods to scale a model
|
|
3
4
|
@class ScaleModelUtils
|
|
@@ -6,8 +7,8 @@
|
|
|
6
7
|
local ScaleModelUtils = {}
|
|
7
8
|
|
|
8
9
|
local CLASS_NAME_TO_MIN_SIZE = {
|
|
9
|
-
["TrussPart"] = Vector3.new(2, 2, 2)
|
|
10
|
-
["UnionOperation"]
|
|
10
|
+
["TrussPart"] = Vector3.new(2, 2, 2),
|
|
11
|
+
["UnionOperation"] = Vector3.zero,
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
local MIN_PART_SIZE = Vector3.new(0.05, 0.05, 0.05)
|
|
@@ -17,7 +18,7 @@ local MIN_PART_SIZE = Vector3.new(0.05, 0.05, 0.05)
|
|
|
17
18
|
@param part BasePart
|
|
18
19
|
@param scale number
|
|
19
20
|
]=]
|
|
20
|
-
function ScaleModelUtils.scalePartSize(part, scale)
|
|
21
|
+
function ScaleModelUtils.scalePartSize(part: BasePart, scale: Vector3 | number)
|
|
21
22
|
local partSize = part.Size
|
|
22
23
|
|
|
23
24
|
local mesh = part:FindFirstChildWhichIsA("DataModelMesh")
|
|
@@ -33,14 +34,12 @@ function ScaleModelUtils.scalePartSize(part, scale)
|
|
|
33
34
|
|
|
34
35
|
local minSize = CLASS_NAME_TO_MIN_SIZE[part.ClassName] or MIN_PART_SIZE
|
|
35
36
|
|
|
36
|
-
if newPartSize.X < minSize.X
|
|
37
|
-
or newPartSize.Y < minSize.Y
|
|
38
|
-
or newPartSize.Z < minSize.Z then
|
|
39
|
-
|
|
37
|
+
if newPartSize.X < minSize.X or newPartSize.Y < minSize.Y or newPartSize.Z < minSize.Z then
|
|
40
38
|
newPartSize = Vector3.new(
|
|
41
39
|
math.max(newPartSize.X, minSize.X),
|
|
42
40
|
math.max(newPartSize.Y, minSize.Y),
|
|
43
|
-
math.max(newPartSize.Z, minSize.Z)
|
|
41
|
+
math.max(newPartSize.Z, minSize.Z)
|
|
42
|
+
)
|
|
44
43
|
|
|
45
44
|
-- We need a mesh for scaling (hopefully)
|
|
46
45
|
mesh = ScaleModelUtils.createMeshFromPart(part)
|
|
@@ -49,7 +48,7 @@ function ScaleModelUtils.scalePartSize(part, scale)
|
|
|
49
48
|
part.Size = newPartSize
|
|
50
49
|
|
|
51
50
|
if mesh then
|
|
52
|
-
mesh.Scale = newRenderSize/newPartSize
|
|
51
|
+
mesh.Scale = newRenderSize / newPartSize
|
|
53
52
|
mesh.Offset = mesh.Offset * scale
|
|
54
53
|
end
|
|
55
54
|
end
|
|
@@ -61,7 +60,7 @@ end
|
|
|
61
60
|
@param scale number
|
|
62
61
|
@param centroid Vector3
|
|
63
62
|
]=]
|
|
64
|
-
function ScaleModelUtils.scalePart(part, scale, centroid)
|
|
63
|
+
function ScaleModelUtils.scalePart(part: BasePart, scale: Vector3 | number, centroid: Vector3)
|
|
65
64
|
assert(typeof(part) == "Instance" and part:IsA("BasePart"), "Bad part")
|
|
66
65
|
|
|
67
66
|
local partPosition = part.Position
|
|
@@ -80,8 +79,8 @@ end
|
|
|
80
79
|
@param scale number -- The scale to scale by
|
|
81
80
|
@param centroid Vector3 -- the center to scale by
|
|
82
81
|
]=]
|
|
83
|
-
function ScaleModelUtils.scale(parts, scale, centroid)
|
|
84
|
-
for _, part in
|
|
82
|
+
function ScaleModelUtils.scale(parts: { BasePart }, scale: number, centroid: Vector3)
|
|
83
|
+
for _, part in parts do
|
|
85
84
|
ScaleModelUtils.scalePart(part, scale, centroid)
|
|
86
85
|
end
|
|
87
86
|
end
|
|
@@ -92,7 +91,7 @@ end
|
|
|
92
91
|
@param part BasePart
|
|
93
92
|
@return Mesh?
|
|
94
93
|
]=]
|
|
95
|
-
function ScaleModelUtils.createMeshFromPart(part)
|
|
94
|
+
function ScaleModelUtils.createMeshFromPart(part: BasePart): FileMesh?
|
|
96
95
|
if part:IsA("WedgePart") then
|
|
97
96
|
local mesh = Instance.new("SpecialMesh")
|
|
98
97
|
mesh.MeshType = Enum.MeshType.Wedge
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
Utility functions involving spheres
|
|
3
4
|
@class SphereUtils
|
|
@@ -15,9 +16,9 @@ local SphereUtils = {}
|
|
|
15
16
|
@return boolean
|
|
16
17
|
]=]
|
|
17
18
|
function SphereUtils.intersectsRay(
|
|
18
|
-
sphereCenter, sphereRadius,
|
|
19
|
-
rayOrigin, rayDirection
|
|
20
|
-
)
|
|
19
|
+
sphereCenter: Vector3, sphereRadius: number,
|
|
20
|
+
rayOrigin: Vector3, rayDirection: Vector3
|
|
21
|
+
): boolean
|
|
21
22
|
local relOrigin = rayOrigin - sphereCenter
|
|
22
23
|
local rr = relOrigin:Dot(relOrigin)
|
|
23
24
|
local dr = rayDirection:Dot(relOrigin)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
Utility functions for surfaces
|
|
3
4
|
@class SurfaceUtils
|
|
@@ -7,9 +8,9 @@ local SurfaceUtils = {}
|
|
|
7
8
|
|
|
8
9
|
local UP = Vector3.new(0, 1, 0)
|
|
9
10
|
local BACK = Vector3.new(0, 0, 1)
|
|
10
|
-
local EXTRASPIN = CFrame.fromEulerAnglesXYZ(math.pi/2, 0, 0)
|
|
11
|
+
local EXTRASPIN = CFrame.fromEulerAnglesXYZ(math.pi / 2, 0, 0)
|
|
11
12
|
|
|
12
|
-
local function getTranstionBetween(v1, v2, pitchAxis)
|
|
13
|
+
local function getTranstionBetween(v1: Vector3, v2: Vector3, pitchAxis: Vector3): CFrame
|
|
13
14
|
local dot = v1:Dot(v2)
|
|
14
15
|
if dot > 0.99999 then
|
|
15
16
|
return CFrame.new()
|
|
@@ -30,7 +31,7 @@ end
|
|
|
30
31
|
@param lnormal Vector3
|
|
31
32
|
@return CFrame
|
|
32
33
|
]=]
|
|
33
|
-
function SurfaceUtils.getSurfaceCFrame(part, lnormal)
|
|
34
|
+
function SurfaceUtils.getSurfaceCFrame(part: BasePart, lnormal: Vector3): CFrame
|
|
34
35
|
local transition = getTranstionBetween(UP, lnormal, BACK)
|
|
35
36
|
return part.CFrame * transition * EXTRASPIN
|
|
36
37
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
--!strict
|
|
1
2
|
--[=[
|
|
2
3
|
Authored by Egomoose, modified by Quenty
|
|
3
4
|
|
|
@@ -10,16 +11,16 @@ local SwingTwistUtils = {}
|
|
|
10
11
|
Decomposes a CFrame into a swing and a twist.
|
|
11
12
|
@param cf CFrame
|
|
12
13
|
@param direction Vector3
|
|
13
|
-
@return
|
|
14
|
-
@return
|
|
14
|
+
@return CFrame -- swing
|
|
15
|
+
@return CFrame -- twist
|
|
15
16
|
]=]
|
|
16
|
-
function SwingTwistUtils.swingTwist(cf, direction)
|
|
17
|
+
function SwingTwistUtils.swingTwist(cf: CFrame, direction: Vector3): (CFrame, CFrame)
|
|
17
18
|
local axis, theta = cf:ToAxisAngle()
|
|
18
19
|
-- convert to quaternion
|
|
19
|
-
local w, v = math.cos(theta/2),
|
|
20
|
+
local w, v = math.cos(theta / 2), math.sin(theta / 2) * axis
|
|
20
21
|
|
|
21
22
|
-- (v . d)*d, plug into CFrame quaternion constructor with w it will solve rest for us
|
|
22
|
-
local proj = v:Dot(direction)*direction
|
|
23
|
+
local proj = v:Dot(direction) * direction
|
|
23
24
|
local twist = CFrame.new(0, 0, 0, proj.X, proj.Y, proj.Z, w)
|
|
24
25
|
|
|
25
26
|
-- cf = swing * twist, thus...
|
|
@@ -33,7 +34,7 @@ end
|
|
|
33
34
|
@param direction Vector3
|
|
34
35
|
@return number
|
|
35
36
|
]=]
|
|
36
|
-
function SwingTwistUtils.twistAngle(cf, direction)
|
|
37
|
+
function SwingTwistUtils.twistAngle(cf: CFrame, direction: Vector3): number
|
|
37
38
|
local axis, theta = cf:ToAxisAngle()
|
|
38
39
|
local w, v = math.cos(theta/2), math.sin(theta/2)*axis
|
|
39
40
|
local proj = v:Dot(direction)*direction
|