@kunosyn/shatterbox 0.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.
- package/LICENSE +21 -0
- package/README.md +35 -0
- package/package.json +36 -0
- package/src/Effects.d.ts +134 -0
- package/src/Effects.luau +114 -0
- package/src/Settings.luau +75 -0
- package/src/index.d.ts +617 -0
- package/src/init.luau +2580 -0
- package/src/lib/Client.luau +4020 -0
- package/src/lib/InitializeShatterboxClients.client.luau +10 -0
- package/src/lib/ObjectCache.luau +198 -0
- package/src/lib/PartOperations.luau +673 -0
- package/src/lib/Server.luau +4425 -0
- package/src/lib/VertexMath.luau +403 -0
- package/src/types.ts +504 -0
- package/src/util/HitboxVisualizer.luau +101 -0
- package/src/util/TaggedArray.luau +66 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
--!native
|
|
2
|
+
--!optimize 2
|
|
3
|
+
|
|
4
|
+
--#selene: allow(unused_variable)
|
|
5
|
+
|
|
6
|
+
local EPSILON = 1e-3
|
|
7
|
+
local UNEPS = 1 - EPSILON
|
|
8
|
+
|
|
9
|
+
local localAxisVectors = { X = "RightVector", Y = "UpVector", Z = "ZVector" }
|
|
10
|
+
|
|
11
|
+
local axisID = {"X", "Y", "Z"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
local PartTypeContainsPoint = {}
|
|
16
|
+
|
|
17
|
+
function PartTypeContainsPoint.Ball(cframe : CFrame, size : Vector3, p : Vector3)
|
|
18
|
+
|
|
19
|
+
local dp = (p - cframe.Position) * UNEPS
|
|
20
|
+
local dx, dy, dz = dp.X, dp.Y, dp.Z
|
|
21
|
+
local minSize = math.min(size.X, size.Y, size.Z) * 0.5
|
|
22
|
+
|
|
23
|
+
return dx*dx + dy*dy + dz*dz <= minSize*minSize
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
function PartTypeContainsPoint.Cylinder(cframe : CFrame, size : Vector3, p : Vector3)
|
|
27
|
+
|
|
28
|
+
local localPoint = cframe:PointToObjectSpace(p):Abs() * UNEPS
|
|
29
|
+
|
|
30
|
+
if localPoint.X > size.X * 0.5 then return false end
|
|
31
|
+
|
|
32
|
+
local dy, dz = localPoint.Y, localPoint.Z
|
|
33
|
+
local minSize = math.min(size.Y, size.Z) * 0.5
|
|
34
|
+
|
|
35
|
+
return dy*dy + dz*dz <= minSize*minSize
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
function PartTypeContainsPoint.Block(cframe : CFrame, size : Vector3, p : Vector3)
|
|
39
|
+
|
|
40
|
+
local localPoint = cframe:PointToObjectSpace(p):Abs() * UNEPS
|
|
41
|
+
|
|
42
|
+
return (localPoint.X <= size.X * 0.5) and (localPoint.Y <= size.Y * 0.5) and (localPoint.Z <= size.Z * 0.5)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
function PartTypeContainsPoint.Wedge(cframe : CFrame, size : Vector3, p : Vector3)
|
|
46
|
+
|
|
47
|
+
local localPoint = cframe:PointToObjectSpace(p) * UNEPS
|
|
48
|
+
local absPoint = localPoint:Abs()
|
|
49
|
+
local wsy, wsz = size.Y, size.Z
|
|
50
|
+
|
|
51
|
+
return (absPoint.X <= size.X * 0.5) and (absPoint.Y <= wsy * 0.5) and (absPoint.Z <= wsz * 0.5) and (localPoint.Y * wsz <= localPoint.Z * wsy)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
function PartTypeContainsPoint.CornerWedge(cframe : CFrame, size : Vector3, p : Vector3)
|
|
55
|
+
|
|
56
|
+
local localPoint = cframe:PointToObjectSpace(p) * UNEPS
|
|
57
|
+
local absPoint = localPoint:Abs()
|
|
58
|
+
local wsx, wsy, wsz = size.X, size.Y, size.Z
|
|
59
|
+
|
|
60
|
+
return (absPoint.X <= wsx * 0.5) and (absPoint.Y <= wsy * 0.5) and (absPoint.Z <= wsz * 0.5) and (localPoint.Y * wsz <= localPoint.Z * -wsy) and (localPoint.Y * wsx <= localPoint.X * wsy)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
local GetVerts = {}
|
|
66
|
+
|
|
67
|
+
function GetVerts.Block(cframe : CFrame, size : Vector3)
|
|
68
|
+
size = size * 0.5
|
|
69
|
+
local pos = cframe.Position
|
|
70
|
+
local lx, ly, lz = cframe.XVector*size.X, cframe.YVector*size.Y, cframe.ZVector*size.Z
|
|
71
|
+
return {
|
|
72
|
+
pos - lx - ly - lz,
|
|
73
|
+
pos - lx - ly + lz,
|
|
74
|
+
pos - lx + ly - lz,
|
|
75
|
+
pos - lx + ly + lz,
|
|
76
|
+
pos + lx - ly - lz,
|
|
77
|
+
pos + lx - ly + lz,
|
|
78
|
+
pos + lx + ly - lz,
|
|
79
|
+
pos + lx + ly + lz
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
local block_edges = { {1,2}, {1,3}, {1,5}, {2,4}, {2,6}, {3,4}, {3,7}, {4,8}, {5,6}, {5,7}, {6,8}, {7,8} }
|
|
83
|
+
|
|
84
|
+
function GetVerts.Wedge(cframe : CFrame, size : Vector3)
|
|
85
|
+
size = size * 0.5
|
|
86
|
+
local pos = cframe.Position
|
|
87
|
+
local lx, ly, lz = cframe.XVector*size.X, cframe.YVector*size.Y, cframe.ZVector*size.Z
|
|
88
|
+
return {
|
|
89
|
+
pos - lx - ly - lz,
|
|
90
|
+
pos - lx - ly + lz,
|
|
91
|
+
pos + lx - ly - lz,
|
|
92
|
+
pos + lx - ly + lz,
|
|
93
|
+
pos - lx + ly + lz,
|
|
94
|
+
pos + lx + ly + lz
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
local wedge_edges = { {1,2}, {2,4}, {4,3}, {3,1}, {2,5}, {4,6}, {5,6}, {1,3}, {1,5}, {3,6} }
|
|
98
|
+
|
|
99
|
+
function GetVerts.CornerWedge(cframe : CFrame, size : Vector3)
|
|
100
|
+
size = size * 0.5
|
|
101
|
+
local pos = cframe.Position
|
|
102
|
+
local lx, ly, lz = cframe.XVector*size.X, cframe.YVector*size.Y, cframe.ZVector*size.Z
|
|
103
|
+
return {
|
|
104
|
+
pos - lx - ly - lz,
|
|
105
|
+
pos - lx - ly + lz,
|
|
106
|
+
pos + lx - ly - lz,
|
|
107
|
+
pos + lx - ly + lz,
|
|
108
|
+
pos + lx + ly - lz
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
local corner_wedge_edges = { {1,2}, {2,4}, {4,3}, {3,1}, {3,5}, {1,5}, {4,5} }
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
local GetNormals = {}
|
|
116
|
+
|
|
117
|
+
function GetNormals.Block(cframe : CFrame)
|
|
118
|
+
local lx, ly, lz = cframe.XVector, cframe.YVector, cframe.ZVector
|
|
119
|
+
return {
|
|
120
|
+
-lx, lx,
|
|
121
|
+
-ly, ly,
|
|
122
|
+
-lz, lz
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
function GetNormals.Wedge(cframe : CFrame, size : Vector3)
|
|
127
|
+
local lx, ly, lz = cframe.XVector, cframe.YVector, cframe.ZVector
|
|
128
|
+
return {
|
|
129
|
+
-cframe.YVector,
|
|
130
|
+
(lz*size.Y - ly*size.Z).Unit,
|
|
131
|
+
-lz,
|
|
132
|
+
-lx, lx
|
|
133
|
+
}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
function GetNormals.CornerWedge(cframe : CFrame, size : Vector3)
|
|
137
|
+
local lx, ly, lz = cframe.XVector, cframe.YVector, cframe.ZVector
|
|
138
|
+
return {
|
|
139
|
+
-ly,
|
|
140
|
+
(lz*size.Y - lx*size.X).Unit,
|
|
141
|
+
(ly*size.Z + lz*size.Y).Unit,
|
|
142
|
+
-lz,
|
|
143
|
+
lx
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
local function ProjectExtent(verts: {Vector3}, axis: Vector3)
|
|
150
|
+
local min, max = nil, nil
|
|
151
|
+
local ax, ay, az = axis.X, axis.Y, axis.Z
|
|
152
|
+
for _, v in verts do
|
|
153
|
+
local d = v.X*ax + v.Y*ay + v.Z*az
|
|
154
|
+
if min then
|
|
155
|
+
min, max = math.min(min, d), math.max(max, d)
|
|
156
|
+
else
|
|
157
|
+
min, max = d, d
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
return min, max
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
local function SAT(VA, NA, VB, NB)
|
|
164
|
+
-- Create candidate axis array and indexer
|
|
165
|
+
local candidates, n = table.create(#NA + #NB + #NA*#NB), 0
|
|
166
|
+
|
|
167
|
+
-- Populate candidate axis
|
|
168
|
+
for _, a in ipairs(NA) do
|
|
169
|
+
n += 1
|
|
170
|
+
candidates[n] = a
|
|
171
|
+
|
|
172
|
+
local ax, ay, az = a.X, a.Y, a.Z
|
|
173
|
+
for _, b in ipairs(NB) do
|
|
174
|
+
n += 1
|
|
175
|
+
candidates[n] = b
|
|
176
|
+
|
|
177
|
+
local bx, by, bz = b.X, b.Y, b.Z
|
|
178
|
+
local cross = Vector3.new(
|
|
179
|
+
ay*bz - az*by,
|
|
180
|
+
az*bx - ax*bz,
|
|
181
|
+
ax*by - ay*bx
|
|
182
|
+
)
|
|
183
|
+
if cross.Magnitude > EPSILON then
|
|
184
|
+
n += 1
|
|
185
|
+
candidates[n] = cross.Unit
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
-- SAT test on all candidate axes
|
|
191
|
+
for _, axis in candidates do
|
|
192
|
+
local min1, max1 = ProjectExtent(VA, axis)
|
|
193
|
+
local min2, max2 = ProjectExtent(VB, axis)
|
|
194
|
+
|
|
195
|
+
if max1 < min2 or max2 < min1 then
|
|
196
|
+
return false -- Separating axis found
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
return true -- No separating axis found
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
local function PartContainsAllVerts(part : Part, verts : {Vector3})
|
|
204
|
+
|
|
205
|
+
local PartContainsPoint = PartTypeContainsPoint[part.Shape.Name]
|
|
206
|
+
local partSize = part.Size
|
|
207
|
+
local partCFrame = part.CFrame
|
|
208
|
+
|
|
209
|
+
for _, vert in ipairs(verts) do
|
|
210
|
+
if PartContainsPoint(partCFrame, partSize, vert) then continue end
|
|
211
|
+
return false
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
return true
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
local function PartEncapsulatesBlockPart(part : Part, blockCFrame : CFrame, blockSize : Vector3)
|
|
218
|
+
|
|
219
|
+
return PartContainsAllVerts(part, GetVerts.Block(blockCFrame, blockSize))
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
local function PartContainsAVert(part : Part, verts : {Vector3})
|
|
223
|
+
|
|
224
|
+
local PartContainsPoint = PartTypeContainsPoint[part.Shape.Name]
|
|
225
|
+
local partSize = part.Size
|
|
226
|
+
local partCFrame = part.CFrame
|
|
227
|
+
|
|
228
|
+
for i, vert in ipairs(verts) do
|
|
229
|
+
|
|
230
|
+
if PartContainsPoint(partCFrame, partSize, vert) then return i end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
return false
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
local function BallIntersectsBlock(sphereCFrame, sphereSize, boxCFrame : CFrame, boxSize : Vector3)
|
|
237
|
+
local radius = math.min(sphereSize.X, sphereSize.Y, sphereSize.Z) * 0.5
|
|
238
|
+
local localCenter = boxCFrame:Inverse() * sphereCFrame.Position
|
|
239
|
+
local halfSize = boxSize * 0.5
|
|
240
|
+
|
|
241
|
+
local clamped = Vector3.new(
|
|
242
|
+
math.clamp(localCenter.X, -halfSize.X, halfSize.X),
|
|
243
|
+
math.clamp(localCenter.Y, -halfSize.Y, halfSize.Y),
|
|
244
|
+
math.clamp(localCenter.Z, -halfSize.Z, halfSize.Z)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return (localCenter - clamped).Magnitude <= radius
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
local function SegmentIntersectsCylinder(L1, L2, C1, C2, radius)
|
|
251
|
+
|
|
252
|
+
local Ca = C2 - C1
|
|
253
|
+
if Ca.Magnitude < EPSILON then return false end
|
|
254
|
+
local n = Ca.Unit
|
|
255
|
+
|
|
256
|
+
local d = L2 - L1
|
|
257
|
+
local m = L1 - C1
|
|
258
|
+
|
|
259
|
+
local md = m - n * m:Dot(n)
|
|
260
|
+
local dd = d - n * d:Dot(n)
|
|
261
|
+
|
|
262
|
+
local a = dd:Dot(dd)
|
|
263
|
+
local b = 2 * dd:Dot(md)
|
|
264
|
+
local c = md:Dot(md) - radius * radius
|
|
265
|
+
|
|
266
|
+
if math.abs(a) < EPSILON then
|
|
267
|
+
-- d parallel to cylinder axis or nearly so
|
|
268
|
+
local projL1 = (L1 - C1):Dot(n)
|
|
269
|
+
local radialL1 = (L1 - C1 - n * projL1).Magnitude
|
|
270
|
+
local projL2 = (L2 - C1):Dot(n)
|
|
271
|
+
local radialL2 = (L2 - C1 - n * projL2).Magnitude
|
|
272
|
+
|
|
273
|
+
local axisLen = Ca.Magnitude
|
|
274
|
+
|
|
275
|
+
if projL1 >= -EPSILON and projL1 <= axisLen + EPSILON and radialL1 <= radius + EPSILON then return true end
|
|
276
|
+
if projL2 >= -EPSILON and projL2 <= axisLen + EPSILON and radialL2 <= radius + EPSILON then return true end
|
|
277
|
+
|
|
278
|
+
return false
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
local discriminant = b * b - 4 * a * c
|
|
282
|
+
if discriminant < 0 then return false end
|
|
283
|
+
|
|
284
|
+
local sqrtDisc = math.sqrt(discriminant)
|
|
285
|
+
local t1 = (-b - sqrtDisc) / (2 * a)
|
|
286
|
+
local t2 = (-b + sqrtDisc) / (2 * a)
|
|
287
|
+
|
|
288
|
+
if t1 >= 0 - EPSILON and t1 <= 1 + EPSILON then
|
|
289
|
+
local point = L1 + d * t1
|
|
290
|
+
local projLength = (point - C1):Dot(n)
|
|
291
|
+
local axisLength = Ca.Magnitude
|
|
292
|
+
if projLength >= 0 - EPSILON and projLength <= axisLength + EPSILON then
|
|
293
|
+
return true
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
if t2 >= 0 - EPSILON and t2 <= 1 + EPSILON then
|
|
298
|
+
local point = L1 + d * t2
|
|
299
|
+
local projLength = (point - C1):Dot(n)
|
|
300
|
+
local axisLength = Ca.Magnitude
|
|
301
|
+
if projLength >= 0 - EPSILON and projLength <= axisLength + EPSILON then
|
|
302
|
+
return true
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
return false
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
local function PlaneIntersectsCylinder(P, S, U, V, C0, C1, radius)
|
|
310
|
+
local N = U:Cross(V).Unit
|
|
311
|
+
local Ca = C1 - C0
|
|
312
|
+
local axisLen = Ca.Magnitude
|
|
313
|
+
if axisLen < 1e-6 then return false end
|
|
314
|
+
|
|
315
|
+
local CaUnit = Ca / axisLen
|
|
316
|
+
local d0 = (C0 - P):Dot(N)
|
|
317
|
+
local d1 = (C1 - P):Dot(N)
|
|
318
|
+
|
|
319
|
+
if d0 > radius and d1 > radius then return false end
|
|
320
|
+
if d0 < -radius and d1 < -radius then return false end
|
|
321
|
+
|
|
322
|
+
local function ProjectOntoPlane(Q)
|
|
323
|
+
local toQ = Q - P
|
|
324
|
+
return Vector2.new(toQ:Dot(U), toQ:Dot(V))
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
local q0 = ProjectOntoPlane(C0)
|
|
328
|
+
local q1 = ProjectOntoPlane(C1)
|
|
329
|
+
local axis2D = q1 - q0
|
|
330
|
+
local center = (q0 + q1) * 0.5
|
|
331
|
+
|
|
332
|
+
local dir = axis2D.Unit
|
|
333
|
+
local halfLen = (axis2D.Magnitude * 0.5)
|
|
334
|
+
|
|
335
|
+
-- radius projects orthogonal to Ca axis in the plane only
|
|
336
|
+
local orthogonalU = (U - CaUnit * U:Dot(CaUnit)).Unit
|
|
337
|
+
local orthogonalV = (V - CaUnit * V:Dot(CaUnit)).Unit
|
|
338
|
+
|
|
339
|
+
local radiusU = radius * orthogonalU.Magnitude
|
|
340
|
+
local radiusV = radius * orthogonalV.Magnitude
|
|
341
|
+
|
|
342
|
+
local px = math.abs(center.X)
|
|
343
|
+
local py = math.abs(center.Y)
|
|
344
|
+
local dx = math.abs(dir.X) * halfLen + radiusU
|
|
345
|
+
local dy = math.abs(dir.Y) * halfLen + radiusV
|
|
346
|
+
|
|
347
|
+
return px <= S.X + dx and py <= S.Y + dy
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
local function CylinderIntersectsBlock(cylinderCFrame, cylinderSize, cylinderBoxVerts, cylinderBoxNormals, boxCFrame, boxSize, boxVerts, boxNormals)
|
|
351
|
+
|
|
352
|
+
local halfAxis = cylinderCFrame.RightVector * (cylinderSize.X * 0.5)
|
|
353
|
+
local p = cylinderCFrame.Position
|
|
354
|
+
local c0, c1 = p - halfAxis, p + halfAxis
|
|
355
|
+
local radius = 0.5 * math.min(cylinderSize.Y, cylinderSize.Z)
|
|
356
|
+
|
|
357
|
+
for _, seg in ipairs(block_edges) do
|
|
358
|
+
if SegmentIntersectsCylinder(boxVerts[seg[1]], boxVerts[seg[2]], c0, c1, radius) then return true end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
if not SAT(cylinderBoxVerts, cylinderBoxNormals, boxVerts, boxNormals) then return false end
|
|
362
|
+
|
|
363
|
+
for i, a in ipairs(axisID) do
|
|
364
|
+
local la = localAxisVectors[a]
|
|
365
|
+
local lcf = boxCFrame[la] * boxSize[a] * 0.5
|
|
366
|
+
|
|
367
|
+
local sf = table.clone(axisID)
|
|
368
|
+
table.remove(sf, i)
|
|
369
|
+
|
|
370
|
+
local UA, VA = sf[1], sf[2]
|
|
371
|
+
local S = Vector2.new(boxSize[UA], boxSize[VA]) * 0.5
|
|
372
|
+
|
|
373
|
+
local U, V = boxCFrame[localAxisVectors[UA]], boxCFrame[localAxisVectors[VA]]
|
|
374
|
+
|
|
375
|
+
if PlaneIntersectsCylinder(boxCFrame.Position + lcf, S, U, V, c0, c1, radius) then return true end
|
|
376
|
+
if PlaneIntersectsCylinder(boxCFrame.Position - lcf, S, U, V, c0, c1, radius) then return true end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
return false
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
local VertexMath = {}
|
|
386
|
+
|
|
387
|
+
VertexMath.GetVerts = GetVerts
|
|
388
|
+
|
|
389
|
+
VertexMath.GetNormals = GetNormals
|
|
390
|
+
|
|
391
|
+
VertexMath.SAT = SAT
|
|
392
|
+
|
|
393
|
+
VertexMath.PartEncapsulatesBlockPart = PartEncapsulatesBlockPart
|
|
394
|
+
|
|
395
|
+
VertexMath.PartContainsAllVerts = PartContainsAllVerts
|
|
396
|
+
|
|
397
|
+
VertexMath.PartContainsAVert = PartContainsAVert
|
|
398
|
+
|
|
399
|
+
VertexMath.BallIntersectsBlock = BallIntersectsBlock
|
|
400
|
+
|
|
401
|
+
VertexMath.CylinderIntersectsBlock = CylinderIntersectsBlock
|
|
402
|
+
|
|
403
|
+
return VertexMath
|