@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,673 @@
|
|
|
1
|
+
--#selene: allow(incorrect_standard_library_use)
|
|
2
|
+
--#selene: allow(multiple_statements)
|
|
3
|
+
|
|
4
|
+
-- PartOperations contains functions that perform operations on an individual part (subdivide, voxelize, etc.)
|
|
5
|
+
|
|
6
|
+
local Settings = require(script.Parent.Parent:WaitForChild("Settings"))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
type PartType = {CFrame : CFrame, Size : Vector3}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
local localAxisVectors = { X = "RightVector", Y = "UpVector", Z = "ZVector" }
|
|
13
|
+
|
|
14
|
+
local WHITE = Color3.new(1, 1, 1)
|
|
15
|
+
local MEDIUMSTONEGREY = Color3.fromRGB(163, 162, 165)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
-- Serializes the given decal instance
|
|
19
|
+
local function SerializedDecalInstance(decal : Decal)
|
|
20
|
+
local serialDecal = {}
|
|
21
|
+
|
|
22
|
+
local prop = decal.Color3
|
|
23
|
+
if prop ~= WHITE then serialDecal.Color3 = prop end
|
|
24
|
+
|
|
25
|
+
prop = #decal.ColorMap > 0 and decal.ColorMap or decal.Texture
|
|
26
|
+
if #prop > 0 then serialDecal.ColorMap = prop end
|
|
27
|
+
|
|
28
|
+
prop = decal.Transparency
|
|
29
|
+
if prop ~= 0 then serialDecal.Transparency = prop end
|
|
30
|
+
|
|
31
|
+
prop = decal.UVOffset
|
|
32
|
+
if prop ~= Vector2.zero then serialDecal.UVOffset = prop end
|
|
33
|
+
prop = decal.UVScale
|
|
34
|
+
if prop ~= Vector2.one then serialDecal.UVScale = prop end
|
|
35
|
+
|
|
36
|
+
prop = decal.ZIndex
|
|
37
|
+
if prop ~= 1 then serialDecal.ZIndex = prop end
|
|
38
|
+
|
|
39
|
+
prop = decal.Face.Value
|
|
40
|
+
if prop ~= 5 then serialDecal.Face = prop end
|
|
41
|
+
|
|
42
|
+
return serialDecal
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
local function DeserializedDecalInstance(serialDecal, ClassName)
|
|
46
|
+
|
|
47
|
+
local decal = Instance.new(ClassName)
|
|
48
|
+
|
|
49
|
+
local prop = serialDecal.Color3
|
|
50
|
+
if prop then decal.Color3 = prop end
|
|
51
|
+
|
|
52
|
+
prop = serialDecal.ColorMap
|
|
53
|
+
if prop then decal.ColorMap = prop end
|
|
54
|
+
|
|
55
|
+
prop = serialDecal.Transparency
|
|
56
|
+
if prop then decal.Transparency = prop end
|
|
57
|
+
|
|
58
|
+
prop = serialDecal.UVOffset
|
|
59
|
+
if prop then decal.UVOffset = prop end
|
|
60
|
+
prop = serialDecal.UVScale
|
|
61
|
+
if prop then decal.UVScale = prop end
|
|
62
|
+
|
|
63
|
+
prop = serialDecal.ZIndex
|
|
64
|
+
if prop then decal.ZIndex = prop end
|
|
65
|
+
|
|
66
|
+
prop = serialDecal.Face
|
|
67
|
+
if prop then decal.Face = Enum.NormalId:FromValue(prop) end
|
|
68
|
+
|
|
69
|
+
return decal
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
--[[
|
|
76
|
+
Creates a new part using the supplied `template`, which is either a Part instance or a serialized Part instance
|
|
77
|
+
|
|
78
|
+
A CFrame can be supplied which is where the part will be placed.
|
|
79
|
+
]]
|
|
80
|
+
local function PartFromTemplate(template, cframe, provider)
|
|
81
|
+
|
|
82
|
+
if not template:IsA("Part") or template.Shape ~= Enum.PartType.Block then
|
|
83
|
+
local cloned = template:Clone()
|
|
84
|
+
if cframe then cloned.CFrame = cframe end
|
|
85
|
+
return cloned
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
local part
|
|
89
|
+
|
|
90
|
+
--debug.profilebegin("create part")
|
|
91
|
+
|
|
92
|
+
if provider and #provider._FreeObjects > 0 then
|
|
93
|
+
part = provider:GetPart(cframe)
|
|
94
|
+
else
|
|
95
|
+
--if provider then warn("cache length exceeded : " .. #provider._FreeObjects) end
|
|
96
|
+
part = Instance.new("Part")
|
|
97
|
+
if cframe then
|
|
98
|
+
part.CFrame = cframe
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
-- Set properties if they are not their defaults
|
|
103
|
+
local prop
|
|
104
|
+
|
|
105
|
+
if not part.Anchored then part.Anchored = true end
|
|
106
|
+
|
|
107
|
+
prop = template.Size
|
|
108
|
+
if prop ~= part.Size then part.Size = prop end
|
|
109
|
+
|
|
110
|
+
--part.Color = Color3.fromHSV(math.random(), 1, 1)
|
|
111
|
+
prop = template.Color
|
|
112
|
+
if prop ~= part.Color then part.Color = prop end
|
|
113
|
+
prop = template.Transparency
|
|
114
|
+
if prop ~= part.Transparency then part.Transparency = prop end
|
|
115
|
+
prop = template.Reflectance
|
|
116
|
+
if prop ~= part.Reflectance then part.Reflectance = prop end
|
|
117
|
+
|
|
118
|
+
prop = template.Material
|
|
119
|
+
if prop ~= part.Material then part.Material = prop end
|
|
120
|
+
prop = template.MaterialVariant
|
|
121
|
+
if prop ~= part.MaterialVariant then part.MaterialVariant = prop end
|
|
122
|
+
|
|
123
|
+
prop = template.CollisionGroup
|
|
124
|
+
if prop ~= part.CollisionGroup then part.CollisionGroup = prop end
|
|
125
|
+
|
|
126
|
+
-- copy surface property values
|
|
127
|
+
prop = template.RightSurface
|
|
128
|
+
if prop ~= part.RightSurface then part.RightSurface = prop end
|
|
129
|
+
prop = template.LeftSurface
|
|
130
|
+
if prop ~= part.LeftSurface then part.LeftSurface = prop end
|
|
131
|
+
prop = template.FrontSurface
|
|
132
|
+
if prop ~= part.FrontSurface then part.FrontSurface = prop end
|
|
133
|
+
prop = template.BackSurface
|
|
134
|
+
if prop ~= part.BackSurface then part.BackSurface = prop end
|
|
135
|
+
prop = template.TopSurface
|
|
136
|
+
if prop ~= part.TopSurface then part.TopSurface = prop end
|
|
137
|
+
prop = template.BottomSurface
|
|
138
|
+
if prop ~= part.BottomSurface then part.BottomSurface = prop end
|
|
139
|
+
|
|
140
|
+
-- copy tags
|
|
141
|
+
for _, tag in ipairs(template:GetTags()) do part:AddTag(tag) end
|
|
142
|
+
|
|
143
|
+
-- copy attributes
|
|
144
|
+
for k, v in pairs(template:GetAttributes()) do part:SetAttribute(k, v) end
|
|
145
|
+
|
|
146
|
+
-- copy textures
|
|
147
|
+
for _, child in ipairs(template:GetChildren()) do
|
|
148
|
+
if child:IsA("Texture") then child:Clone().Parent = part end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
--debug.profileend()
|
|
152
|
+
|
|
153
|
+
return part
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
--[[
|
|
160
|
+
Deserializes the given serialized part instance `p`
|
|
161
|
+
]]
|
|
162
|
+
local function DeserializedPartInstance(p, provider)
|
|
163
|
+
|
|
164
|
+
local part
|
|
165
|
+
|
|
166
|
+
if provider and #provider._FreeObjects > 0 then
|
|
167
|
+
part = provider:GetPart(p.CFrame)
|
|
168
|
+
else
|
|
169
|
+
--if provider then warn("cache length exceeded : " .. #provider._FreeObjects) end
|
|
170
|
+
part = Instance.new("Part")
|
|
171
|
+
if p.CFrame then
|
|
172
|
+
part.CFrame = p.CFrame
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if p.Anchored then part.Anchored = true end
|
|
177
|
+
|
|
178
|
+
-- deserialize properties
|
|
179
|
+
local prop
|
|
180
|
+
|
|
181
|
+
prop = p.Size
|
|
182
|
+
if prop then part.Size = prop end
|
|
183
|
+
--part.Color = Color3.fromHSV(math.random(), 1, 1)
|
|
184
|
+
prop = p.Color
|
|
185
|
+
if prop then part.Color = prop end
|
|
186
|
+
prop = p.Transparency
|
|
187
|
+
if prop then part.Transparency = prop end
|
|
188
|
+
prop = p.Reflectance
|
|
189
|
+
if prop then part.Reflectance = prop end
|
|
190
|
+
prop = p.Material
|
|
191
|
+
if prop then part.Material = Enum.Material:FromValue(prop) end
|
|
192
|
+
prop = p.MaterialVariant
|
|
193
|
+
if prop then part.MaterialVariant = prop end
|
|
194
|
+
prop = p.CollisionGroup
|
|
195
|
+
if prop then part.CollisionGroup = prop end
|
|
196
|
+
|
|
197
|
+
-- deserialize surface property values
|
|
198
|
+
prop = p.RightSurface
|
|
199
|
+
if prop then part.RightSurface = Enum.SurfaceType:FromValue(prop) end
|
|
200
|
+
prop = p.LeftSurface
|
|
201
|
+
if prop then part.LeftSurface = Enum.SurfaceType:FromValue(prop) end
|
|
202
|
+
prop = p.FrontSurface
|
|
203
|
+
if prop then part.FrontSurface = Enum.SurfaceType:FromValue(prop) end
|
|
204
|
+
prop = p.BackSurface
|
|
205
|
+
if prop then part.BackSurface = Enum.SurfaceType:FromValue(prop) end
|
|
206
|
+
prop = p.TopSurface
|
|
207
|
+
if prop then part.TopSurface = Enum.SurfaceType:FromValue(prop) end
|
|
208
|
+
prop = p.BottomSurface
|
|
209
|
+
if prop then part.BottomSurface = Enum.SurfaceType:FromValue(prop) end
|
|
210
|
+
|
|
211
|
+
-- deserialize tags
|
|
212
|
+
for _, tag in ipairs(p.Tags) do part:AddTag(tag) end
|
|
213
|
+
|
|
214
|
+
-- deserialize attributes
|
|
215
|
+
for k, v in pairs(p.Attributes) do part:SetAttribute(k, v) end
|
|
216
|
+
|
|
217
|
+
-- deserialize decals
|
|
218
|
+
if p.Decals then
|
|
219
|
+
for _, serialDecal in ipairs(p.Decals) do
|
|
220
|
+
local instancedDecal = DeserializedDecalInstance(serialDecal, "Decal")
|
|
221
|
+
instancedDecal.Parent = part
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
-- deserialize textures
|
|
226
|
+
if p.Textures then
|
|
227
|
+
for _, serialTexture in ipairs(p.Textures) do
|
|
228
|
+
local instancedTexture = DeserializedDecalInstance(serialTexture, "Texture")
|
|
229
|
+
prop = serialTexture.OffsetStudsU
|
|
230
|
+
if prop then instancedTexture.OffsetStudsU = prop end
|
|
231
|
+
prop = serialTexture.OffsetStudsV
|
|
232
|
+
if prop then instancedTexture.OffsetStudsV = prop end
|
|
233
|
+
prop = serialTexture.StudsPerTileU
|
|
234
|
+
if prop then instancedTexture.StudsPerTileU = prop end
|
|
235
|
+
prop = serialTexture.StudsPerTileV
|
|
236
|
+
if prop then instancedTexture.StudsPerTileV = prop end
|
|
237
|
+
|
|
238
|
+
instancedTexture.Parent = part
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
-- deserialize lights
|
|
243
|
+
if p.Lights then
|
|
244
|
+
for _, serialLight in ipairs(p.Lights) do
|
|
245
|
+
local light = Instance.new(serialLight.ClassName)
|
|
246
|
+
|
|
247
|
+
if serialLight.Shadows then light.Shadows = true end
|
|
248
|
+
prop = serialLight.Brightness
|
|
249
|
+
if prop then light.Brightness = prop end
|
|
250
|
+
prop = serialLight.Color
|
|
251
|
+
if prop then light.Color = prop end
|
|
252
|
+
prop = serialLight.Range
|
|
253
|
+
if prop then light.Range = prop end
|
|
254
|
+
|
|
255
|
+
if serialLight.ClassName ~= "PointLight" then
|
|
256
|
+
prop = serialLight.Angle
|
|
257
|
+
if prop then light.Angle = prop end
|
|
258
|
+
prop = serialLight.Face
|
|
259
|
+
if prop then light.Face = Enum.NormalId:FromValue(prop) end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
light.Parent = part
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
return part
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
--[[
|
|
272
|
+
Serializes the given part instance `p`
|
|
273
|
+
]]
|
|
274
|
+
local function SerializedPartInstance(p, ignoreWorldState)
|
|
275
|
+
local serial = {
|
|
276
|
+
Tags = p:GetTags(),
|
|
277
|
+
Attributes = p:GetAttributes()
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
-- serialize properties if they are not their defaults
|
|
281
|
+
local prop
|
|
282
|
+
|
|
283
|
+
serial.Anchored = p.Anchored
|
|
284
|
+
|
|
285
|
+
prop = p.Color
|
|
286
|
+
if prop ~= MEDIUMSTONEGREY then serial.Color = prop end
|
|
287
|
+
prop = p.Transparency
|
|
288
|
+
if prop ~= 0 then serial.Transparency = prop end
|
|
289
|
+
prop = p.Reflectance
|
|
290
|
+
if prop ~= 0 then serial.Reflectance = prop end
|
|
291
|
+
prop = p.Material
|
|
292
|
+
if prop ~= Enum.Material.Plastic then serial.Material = prop.Value end
|
|
293
|
+
prop = p.MaterialVariant
|
|
294
|
+
if #prop > 0 then serial.MaterialVariant = prop end
|
|
295
|
+
prop = p.CollisionGroup
|
|
296
|
+
if prop ~= "Default" then serial.CollisionGroup = prop end
|
|
297
|
+
|
|
298
|
+
-- serialize surface property values if they are not their defaults
|
|
299
|
+
prop = p.RightSurface.Value
|
|
300
|
+
if prop ~= 0 then serial.RightSurface = prop end
|
|
301
|
+
prop = p.LeftSurface.Value
|
|
302
|
+
if prop ~= 0 then serial.LeftSurface = prop end
|
|
303
|
+
prop = p.FrontSurface.Value
|
|
304
|
+
if prop ~= 0 then serial.FrontSurface = prop end
|
|
305
|
+
prop = p.BackSurface.Value
|
|
306
|
+
if prop ~= 0 then serial.BackSurface = prop end
|
|
307
|
+
prop = p.TopSurface.Value
|
|
308
|
+
if prop ~= 3 then serial.TopSurface = prop end
|
|
309
|
+
prop = p.BottomSurface.Value
|
|
310
|
+
if prop ~= 4 then serial.BottomSurface = prop end
|
|
311
|
+
|
|
312
|
+
-- serialize decals, textures, and lights
|
|
313
|
+
local decals, textures, lights = {}, {}, {}
|
|
314
|
+
for _, child in ipairs(p:GetChildren()) do
|
|
315
|
+
if child:IsA("Texture") then
|
|
316
|
+
local serialTexture = SerializedDecalInstance(child)
|
|
317
|
+
prop = child.OffsetStudsU
|
|
318
|
+
if prop ~= 0 then serialTexture.OffsetStudsU = prop end
|
|
319
|
+
prop = child.OffsetStudsV
|
|
320
|
+
if prop ~= 0 then serialTexture.OffsetStudsV = prop end
|
|
321
|
+
prop = child.StudsPerTileU
|
|
322
|
+
if prop ~= 2 then serialTexture.StudsPerTileU = prop end
|
|
323
|
+
prop = child.StudsPerTileV
|
|
324
|
+
if prop ~= 2 then serialTexture.StudsPerTileV = prop end
|
|
325
|
+
|
|
326
|
+
table.insert(textures, serialTexture)
|
|
327
|
+
elseif child:IsA("Decal") then
|
|
328
|
+
table.insert(decals, SerializedDecalInstance(child))
|
|
329
|
+
elseif child:IsA("Light") then
|
|
330
|
+
local className = child.ClassName
|
|
331
|
+
local isAPointLight = className == "PointLight"
|
|
332
|
+
|
|
333
|
+
local serialLight = { ClassName = className }
|
|
334
|
+
|
|
335
|
+
if child.Shadows then serialLight.Shadows = true end
|
|
336
|
+
prop = child.Brightness
|
|
337
|
+
if prop ~= 1 then serialLight.Brightness = prop end
|
|
338
|
+
prop = child.Color
|
|
339
|
+
if prop ~= WHITE then serialLight.Color = prop end
|
|
340
|
+
prop = child.Range
|
|
341
|
+
if (isAPointLight and prop ~= 8) or (not isAPointLight and prop ~= 16) then serialLight.Range = prop end
|
|
342
|
+
|
|
343
|
+
if not isAPointLight then
|
|
344
|
+
prop = child.Angle
|
|
345
|
+
if prop ~= 90 then serialLight.Angle = prop end
|
|
346
|
+
prop = child.Face.Value
|
|
347
|
+
if prop ~= 5 then serialLight.Face = prop end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
table.insert(lights, serialLight)
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
if #decals > 0 then serial.Decals = decals end
|
|
354
|
+
if #textures > 0 then serial.Textures = textures end
|
|
355
|
+
if #lights > 0 then serial.Lights = lights end
|
|
356
|
+
|
|
357
|
+
if ignoreWorldState then return serial end
|
|
358
|
+
|
|
359
|
+
serial.CFrame = p.CFrame
|
|
360
|
+
serial.Size = p.Size
|
|
361
|
+
|
|
362
|
+
return serial
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
--[[
|
|
369
|
+
Returns all of the relevant information to construct a grid of voxels using the specified parameters
|
|
370
|
+
|
|
371
|
+
Return values (in order):
|
|
372
|
+
|
|
373
|
+
\> Minimum cframe (first voxel) : CFrame
|
|
374
|
+
|
|
375
|
+
\> Maximum cframe (last voxel) : CFrame
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
\> Voxel size : Vector3
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
\> Local axis X scaled by voxel size X : Vector3
|
|
382
|
+
|
|
383
|
+
\> Local axis Y scaled by voxel size Y : Vector3
|
|
384
|
+
|
|
385
|
+
\> Local axis Z scaled by voxel size Z : Vector3
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
\> Voxel count X : number
|
|
389
|
+
|
|
390
|
+
\> Voxel count Y : number
|
|
391
|
+
|
|
392
|
+
\> Voxel count Z : number
|
|
393
|
+
]]
|
|
394
|
+
local function GridInfo(PartCFrame : CFrame, PartSize : Vector3, GridSize : number)
|
|
395
|
+
local DIM = Vector3.one:Max(PartSize // GridSize)
|
|
396
|
+
local NVX, NVY, NVZ = DIM.X, DIM.Y, DIM.Z
|
|
397
|
+
|
|
398
|
+
local VoxelSize = PartSize / DIM
|
|
399
|
+
local VSX, VSY, VSZ = VoxelSize.X, VoxelSize.Y, VoxelSize.Z
|
|
400
|
+
|
|
401
|
+
local lsx, lsy, lsz = PartCFrame.XVector*VSX, PartCFrame.YVector*VSY, PartCFrame.ZVector*VSZ
|
|
402
|
+
|
|
403
|
+
local ExtentOffset = 0.5*( lsx*(NVX - 1) + lsy*(NVY - 1) + lsz*(NVZ - 1) )
|
|
404
|
+
|
|
405
|
+
return (PartCFrame - ExtentOffset):Orthonormalize(),
|
|
406
|
+
(PartCFrame + ExtentOffset):Orthonormalize(),
|
|
407
|
+
VoxelSize,
|
|
408
|
+
lsx, lsy, lsz,
|
|
409
|
+
NVX, NVY, NVZ
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
--[[ I wonder if anyone has done this before.
|
|
416
|
+
|
|
417
|
+
Voxelizes a part (or imaginary part) completely according to the GridSize supplied. Returns an array of imaginary box parts.
|
|
418
|
+
|
|
419
|
+
The returned table is created with proper allocation size using `table.create`
|
|
420
|
+
]]
|
|
421
|
+
local function ImaginaryVoxelize(Part : PartType, GridSize : number?) : {PartType}
|
|
422
|
+
|
|
423
|
+
GridSize = GridSize or Settings.DefaultGridSize
|
|
424
|
+
|
|
425
|
+
local count = Vector3.one:Max(Part.Size // GridSize)
|
|
426
|
+
if count.X*count.Y*count.Z == 1 then
|
|
427
|
+
return {Part}
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
local O, _, S, lsx, lsy, lsz, NVX, NVY, NVZ = GridInfo(Part.CFrame, Part.Size, GridSize)
|
|
431
|
+
|
|
432
|
+
local imaginaryParts, index = table.create(NVX*NVY*NVZ), 0
|
|
433
|
+
|
|
434
|
+
for X = 0, NVX - 1 do
|
|
435
|
+
local dx = X*lsx
|
|
436
|
+
for Y = 0, NVY - 1 do
|
|
437
|
+
local dy = Y*lsy
|
|
438
|
+
for Z = 0, NVZ - 1 do
|
|
439
|
+
index += 1
|
|
440
|
+
imaginaryParts[index] = {
|
|
441
|
+
CFrame = (O + dx + dy + Z*lsz):Orthonormalize(),
|
|
442
|
+
Size = S
|
|
443
|
+
}
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
return imaginaryParts
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
--[[
|
|
455
|
+
Completely voxelizes a given part down to a specified GridSize, or uses the default GridSize specified in the Settings.
|
|
456
|
+
|
|
457
|
+
Returns the created voxels as an array.
|
|
458
|
+
|
|
459
|
+
Optimized by using math to fully subdivide a part before creating only the final voxels. However, it effectively has an O(N^3) runtime due to the nature of 3D grids.
|
|
460
|
+
]]
|
|
461
|
+
local function Voxelize(Part : Part, GridSize : number?) : {Part}
|
|
462
|
+
|
|
463
|
+
GridSize = GridSize or Settings.DefaultGridSize
|
|
464
|
+
|
|
465
|
+
local count = Vector3.one:Max(Part.Size // GridSize)
|
|
466
|
+
if count.X*count.Y*count.Z == 1 then
|
|
467
|
+
return {Part}
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
local O, _, S, lsx, lsy, lsz, NVX, NVY, NVZ = GridInfo(Part.CFrame, Part.Size, GridSize)
|
|
471
|
+
|
|
472
|
+
local createdParts, index = table.create(NVX*NVY*NVZ), 0
|
|
473
|
+
|
|
474
|
+
for X = 0, NVX - 1 do
|
|
475
|
+
local dx = X*lsx
|
|
476
|
+
for Y = 0, NVY - 1 do
|
|
477
|
+
local dy = Y*lsy
|
|
478
|
+
for Z = 0, NVZ - 1 do
|
|
479
|
+
index += 1
|
|
480
|
+
|
|
481
|
+
local created = PartFromTemplate(Part, O + dx + dy + Z*lsz)
|
|
482
|
+
created.Size = S
|
|
483
|
+
created.Parent = Part.Parent
|
|
484
|
+
|
|
485
|
+
createdParts[index] = created
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
Part:Destroy()
|
|
491
|
+
|
|
492
|
+
return createdParts
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
--[[ Modified Octree Subdivide
|
|
499
|
+
|
|
500
|
+
Does not mutate the supplied Part reference, which is meant to be an object with the structure {Size : Vector3, CFrame : CFrame}
|
|
501
|
+
|
|
502
|
+
Returns an array with the structure { {Size : Vector3, CFrame : CFrame} }
|
|
503
|
+
|
|
504
|
+
If possible, calculates the division of a part into 2, 4, or 8 pieces.
|
|
505
|
+
|
|
506
|
+
Maintains grid structure for odd parity divisions.
|
|
507
|
+
]]
|
|
508
|
+
local function SubdivideOctree(Part : PartType, GridSize : number)
|
|
509
|
+
local PartSize = Part.Size
|
|
510
|
+
local Axis = {}
|
|
511
|
+
|
|
512
|
+
-- gather all valid subdivision axis
|
|
513
|
+
for axis in pairs(localAxisVectors) do
|
|
514
|
+
if PartSize[axis] * 0.5 < GridSize then continue end
|
|
515
|
+
table.insert(Axis, axis)
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
if #Axis == 0 then return nil end
|
|
519
|
+
|
|
520
|
+
local createdParts = { { CFrame = Part.CFrame, Size = PartSize } }
|
|
521
|
+
local tmpParts = {}
|
|
522
|
+
|
|
523
|
+
-- Subdivide all createdParts along each valid axis, updating the createdParts array.
|
|
524
|
+
for _, axis in pairs(Axis) do
|
|
525
|
+
local ax, ay, az = axis == "X", axis == "Y", axis == "Z"
|
|
526
|
+
local l_axis = localAxisVectors[axis]
|
|
527
|
+
|
|
528
|
+
for _, part in pairs(createdParts) do
|
|
529
|
+
local size = part.Size
|
|
530
|
+
local hs, cframe = size[axis] * 0.5, part.CFrame
|
|
531
|
+
local la = cframe[l_axis]
|
|
532
|
+
|
|
533
|
+
local numVoxels = size[axis] // GridSize
|
|
534
|
+
if numVoxels % 2 == 0 then
|
|
535
|
+
|
|
536
|
+
local hla = la * 0.5 * hs
|
|
537
|
+
local ps = Vector3.new( (ax and hs or size.X), (ay and hs or size.Y), (az and hs or size.Z) )
|
|
538
|
+
table.insert(tmpParts, {
|
|
539
|
+
CFrame = (cframe + hla):Orthonormalize(),
|
|
540
|
+
Size = ps
|
|
541
|
+
})
|
|
542
|
+
table.insert(tmpParts, {
|
|
543
|
+
CFrame = (cframe - hla):Orthonormalize(),
|
|
544
|
+
Size = ps
|
|
545
|
+
})
|
|
546
|
+
else
|
|
547
|
+
local vs = size[axis] / numVoxels
|
|
548
|
+
local FHV, CHV = vs * math.floor(numVoxels * 0.5), vs * math.ceil(numVoxels * 0.5)
|
|
549
|
+
|
|
550
|
+
local off = CHV * 0.5
|
|
551
|
+
table.insert(tmpParts, {
|
|
552
|
+
CFrame = (cframe - off * la):Orthonormalize(),
|
|
553
|
+
Size = Vector3.new(
|
|
554
|
+
ax and FHV or size.X,
|
|
555
|
+
ay and FHV or size.Y,
|
|
556
|
+
az and FHV or size.Z
|
|
557
|
+
)
|
|
558
|
+
})
|
|
559
|
+
off = FHV * 0.5
|
|
560
|
+
table.insert(tmpParts, {
|
|
561
|
+
CFrame = (cframe + off * la):Orthonormalize(),
|
|
562
|
+
Size = Vector3.new(
|
|
563
|
+
ax and CHV or size.X,
|
|
564
|
+
ay and CHV or size.Y,
|
|
565
|
+
az and CHV or size.Z
|
|
566
|
+
)
|
|
567
|
+
})
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
createdParts = tmpParts
|
|
572
|
+
tmpParts = {}
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
return createdParts
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
--[[ Modified KD-tree Subdivide
|
|
582
|
+
|
|
583
|
+
Does not mutate the supplied Part reference, which is meant to be an object with the structure {Size : Vector3, CFrame : CFrame}
|
|
584
|
+
|
|
585
|
+
Returns an array with the structure { {Size : Vector3, CFrame : CFrame} }
|
|
586
|
+
|
|
587
|
+
If possible, calculates the division of a part into 2 pieces.
|
|
588
|
+
|
|
589
|
+
Maintains grid structure for odd parity divisions.
|
|
590
|
+
]]
|
|
591
|
+
local function SubdivideKD(Part : PartType, GridSize : number)
|
|
592
|
+
local PartSize = Part.Size
|
|
593
|
+
local axis, maxSize
|
|
594
|
+
|
|
595
|
+
for a in pairs(localAxisVectors) do
|
|
596
|
+
local PartSizeAxis = PartSize[a]
|
|
597
|
+
if PartSizeAxis * 0.5 < GridSize then continue end
|
|
598
|
+
if axis and maxSize > PartSizeAxis then continue end
|
|
599
|
+
axis, maxSize = a, PartSizeAxis
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
if not axis then return nil end
|
|
603
|
+
|
|
604
|
+
local hs = PartSize[axis] * 0.5
|
|
605
|
+
local cframe = Part.CFrame
|
|
606
|
+
|
|
607
|
+
local numVoxels = PartSize[axis] // GridSize
|
|
608
|
+
if numVoxels % 2 == 0 then
|
|
609
|
+
|
|
610
|
+
local hla = cframe[localAxisVectors[axis]] * 0.5 * hs
|
|
611
|
+
|
|
612
|
+
local ps = Vector3.new(
|
|
613
|
+
axis == "X" and hs or PartSize.X,
|
|
614
|
+
axis == "Y" and hs or PartSize.Y,
|
|
615
|
+
axis == "Z" and hs or PartSize.Z
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
return { { CFrame = (cframe + hla):Orthonormalize(), Size = ps }, { CFrame = (cframe - hla):Orthonormalize(), Size = ps } }
|
|
619
|
+
else
|
|
620
|
+
local AX, AY, AZ = axis == "X", axis == "Y", axis == "Z"
|
|
621
|
+
local la = cframe[localAxisVectors[axis]]
|
|
622
|
+
local vs = PartSize[axis] / numVoxels
|
|
623
|
+
|
|
624
|
+
local createdParts = table.create(2)
|
|
625
|
+
|
|
626
|
+
local FHV, CHV = vs * math.floor(numVoxels * 0.5), vs * math.ceil(numVoxels * 0.5)
|
|
627
|
+
|
|
628
|
+
local off = CHV * 0.5
|
|
629
|
+
createdParts[1] = {
|
|
630
|
+
CFrame = (cframe - off * la):Orthonormalize(),
|
|
631
|
+
Size = Vector3.new(
|
|
632
|
+
AX and FHV or PartSize.X,
|
|
633
|
+
AY and FHV or PartSize.Y,
|
|
634
|
+
AZ and FHV or PartSize.Z
|
|
635
|
+
)
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
off = FHV * 0.5
|
|
639
|
+
createdParts[2] = {
|
|
640
|
+
CFrame = (cframe + off * la):Orthonormalize(),
|
|
641
|
+
Size = Vector3.new(
|
|
642
|
+
AX and CHV or PartSize.X,
|
|
643
|
+
AY and CHV or PartSize.Y,
|
|
644
|
+
AZ and CHV or PartSize.Z
|
|
645
|
+
)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return createdParts
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
local PartOperations = {}
|
|
656
|
+
|
|
657
|
+
PartOperations.PartFromTemplate = PartFromTemplate
|
|
658
|
+
|
|
659
|
+
PartOperations.DeserializedPartInstance = DeserializedPartInstance
|
|
660
|
+
|
|
661
|
+
PartOperations.SerializedPartInstance = SerializedPartInstance
|
|
662
|
+
|
|
663
|
+
PartOperations.GridInfo = GridInfo
|
|
664
|
+
|
|
665
|
+
PartOperations.ImaginaryVoxelize = ImaginaryVoxelize
|
|
666
|
+
|
|
667
|
+
PartOperations.Voxelize = Voxelize
|
|
668
|
+
|
|
669
|
+
PartOperations.SubdivideOctree = SubdivideOctree
|
|
670
|
+
|
|
671
|
+
PartOperations.SubdivideKD = SubdivideKD
|
|
672
|
+
|
|
673
|
+
return PartOperations
|