@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.
@@ -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