@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 savruun
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,35 @@
1
+ <div align="center">
2
+ <p>
3
+ <img src=https://i.imgur.com/j2pg7CB.png />
4
+ </p>
5
+ </div>
6
+
7
+ <div align="center">
8
+ <a href=https://www.npmjs.com/package/@rbxts*/shatterbox>
9
+ <img src=https://img.shields.io/npm/v/@rbxts*/shatterbox />
10
+ </a>
11
+ <a href=https://www.npmjs.com/package/@rbxts*/shatterbox>
12
+ <img src=https://img.shields.io/npm/dt/rbxts-shatterbox />
13
+ </a>
14
+ </div>
15
+
16
+ ## About
17
+
18
+
19
+ > [!IMPORTANT]
20
+ > I am also NOT currently in the rbxts org, this means you will have to drag shatterbox into your node_modules/@rbxts folder before use.
21
+
22
+
23
+ ### Installation
24
+ Install package via [NPM](https://www.npmjs.com/package/@rbxts*/shatterbox):
25
+
26
+ ```sh
27
+ npm i rbxts-shatterbox
28
+ ```
29
+
30
+ Drag shatterbox folder from @rbxts*/shatterbox into node_modules/@rbxts.
31
+
32
+ *Done!*
33
+
34
+ ### Usage Example
35
+ Here's some basic examples of using the shatterbox module:
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@kunosyn/shatterbox",
3
+ "version": "0.0.1",
4
+ "description": "Voxel destruction, simple and optimized.",
5
+ "keywords": [
6
+ "roblox-ts",
7
+ "rbxts",
8
+ "voxel",
9
+ "destruction",
10
+ "shatterbox"
11
+ ],
12
+ "homepage": "https://github.com/savruun/rbxts-shatterbox#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/savruun/rbxts-shatterbox/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/savruun/rbxts-shatterbox.git"
19
+ },
20
+ "license": "MIT",
21
+ "author": "kunosyn",
22
+ "main": "src/init.luau",
23
+ "types": "src/index.d.ts",
24
+ "files": [
25
+ "src"
26
+ ],
27
+ "scripts": {
28
+ "build": "rbxtsc --rojo dev.project.json"
29
+ },
30
+ "devDependencies": {
31
+ "@rbxts/compiler-types": "^3.0.0-types.0",
32
+ "@rbxts/types": "^1.0.907",
33
+ "roblox-ts": "^3.0.0",
34
+ "typescript": "^5.9.3"
35
+ }
36
+ }
@@ -0,0 +1,134 @@
1
+ /// <reference types="@rbxts/types" />
2
+
3
+ import { DestroyedVoxelInfo } from "./types";
4
+
5
+ interface Effects {
6
+ Default: (voxel: BasePart, info: DestroyedVoxelInfo) => void;
7
+ BumpyFloorBreakWalls: (voxel: BasePart, info: DestroyedVoxelInfo) => void;
8
+ Rough: (voxel: BasePart, info: DestroyedVoxelInfo) => void;
9
+
10
+ /**
11
+ * This effect assumes a spherical hitbox (Shape "Ball").
12
+ * The inner 33% of voxels are removed.
13
+ * Starting at the point where voxels are no longer removed, voxels are black. There is a gradient until around the edges, where the voxels should be their original color.
14
+ */
15
+ MapVoxelSpace: (voxel: BasePart, info: DestroyedVoxelInfo) => void;
16
+ }
17
+
18
+ declare const Effects: Effects;
19
+ export = Effects
20
+
21
+ // local Effects = { }
22
+
23
+ // local function map(x, a_0, a_1, b_0, b_1)
24
+ // return b_0 + (b_1 - b_0)*(x - a_0)/(a_1 - a_0)
25
+ // end
26
+
27
+ // local raycastParams = RaycastParams.new()
28
+ // raycastParams.FilterType = Enum.RaycastFilterType.Include
29
+
30
+ // local Shatterbox
31
+ // Effects._SetRef = function(shatterbox)
32
+ // Shatterbox = shatterbox
33
+ // end
34
+
35
+ // Effects.Default = function(voxel)
36
+ // voxel:Destroy()
37
+ // end
38
+
39
+ // Effects.BumpyFloorBreakWalls = function(voxel, info)
40
+ // if info.IsAlreadyDebris then return end
41
+
42
+ // local O = info.UserData.CastOrigin or info.CuttingPart.CFrame.Position
43
+ // local toVoxel = voxel.Position - O
44
+ // -- retrieves the untouched original part, to check if the voxel should be considered a floor
45
+ // local castTo = Shatterbox:GetOriginalPart(info.DirtyGroupID)
46
+
47
+ // raycastParams.FilterDescendantsInstances = { castTo }
48
+
49
+ // castTo.Parent = workspace
50
+ // local hitResult = workspace:Raycast(O - toVoxel * 0.01, toVoxel * 1.01, raycastParams)
51
+ // castTo.Parent = nil
52
+
53
+ // if hitResult and hitResult.Normal:FuzzyEq(Vector3.yAxis, 0.01) then -- if the normal is facing up, it is a floor
54
+
55
+ // voxel.Position += Vector3.yAxis * (math.random() - 0.5) -- add some random vertical offset to floor voxels
56
+
57
+ // else -- everything else becomes a falling voxel using puppeteer
58
+
59
+ // Shatterbox:Puppeteer(voxel) -- start controlled replication of "voxel" to clients (tween between replication of CFrames)
60
+
61
+ // voxel.AssemblyLinearVelocity = toVoxel.Unit * (math.random() * 25 + 50)
62
+ // end
63
+ // end
64
+
65
+
66
+ // function Effects.Rough (voxel, info)
67
+ // if info.IsAlreadyDebris then -- the voxel is already debris
68
+ // -- fully contained debris is destroyed
69
+ // if not info.IsEdge then
70
+ // voxel:Destroy()
71
+ // end
72
+
73
+ // return
74
+ // end
75
+
76
+ // local roll = math.random()
77
+
78
+ // if info.IsEdge and roll < 0.8 then -- 80% of edge voxels become "edge debris"
79
+ // voxel.CFrame *= CFrame.Angles(math.random() * 2 * math.pi, math.random() * 2 * math.pi, math.random() * 2 * math.pi)
80
+ // voxel.Size *= math.random() + 1
81
+ // return
82
+ // end
83
+
84
+ // -- a small percentage of voxels are destroyed
85
+ // if roll > 0.05 then voxel:Destroy()
86
+ // return;
87
+ // end
88
+
89
+ // -- all other voxels are flying debris
90
+
91
+ // voxel.Anchored = false
92
+ // voxel.CanCollide = false
93
+ // voxel.AssemblyLinearVelocity = (Vector3.new(math.random(), math.random(), math.random()) - Vector3.one * 0.5) * 80
94
+ // voxel.AssemblyAngularVelocity = (Vector3.new(math.random(), math.random(), math.random()) - Vector3.one * 0.5) * 20
95
+
96
+ // -- remember to destroy the voxel
97
+ // game.Debris:AddItem(voxel, 3)
98
+ // end
99
+
100
+ // -- This effect assumes a spherical hitbox (Shape "Ball").
101
+ // -- This is an example show you how to use voxel space to perform calculations that are consistent regardless of the gridsize or orientation of voxels.
102
+ // -- The inner 33% of voxels are removed.
103
+ // -- Starting at the point where voxels are no longer removed, voxels are black. There is a gradient until around the edges, where the voxels should be their original color.
104
+ // function Effects.MapVoxelSpace(voxel, info)
105
+ // if info.IsAlreadyDebris then return end
106
+
107
+ // -- the radius of the spherical hitbox
108
+ // local R = info.CuttingPart.Size * 0.5
109
+
110
+ // -- from the center of the sphere, how many voxels away the edge of the sphere is on each axis (in local space)
111
+ // local MaxVoxelDist = Shatterbox:VoxelCountVector(voxel, math.min(R.X, R.Y, R.Z)*Vector3.one)
112
+
113
+ // -- from the center of the current voxel, how many voxels away the center of the sphere is on each axis (in local space)
114
+ // local VoxelDist = Shatterbox:VoxelDistanceVector(voxel, info.CuttingPart.CFrame.Position):Min(MaxVoxelDist)
115
+
116
+ // -- The magnitude of this vector is guaranteed to be in the range 0 to close to 1 because spheres hold a consistent magnitude
117
+ // local NormalDist = (VoxelDist / MaxVoxelDist).Magnitude
118
+
119
+ // -- destroy voxels close to the center (inner 33% by default)
120
+ // local NormalDistThreshold = 0.33
121
+
122
+ // if NormalDist < NormalDistThreshold then
123
+ // voxel:Destroy()
124
+ // return
125
+ // end
126
+
127
+ // -- map and clamp the range [NormalDistThreshold, 1] to the range [1, 0]
128
+ // -- So where the voxels begin to appear, they start at black, and where the voxels end, they should be their original color.
129
+ // local Shade = math.clamp(map(NormalDist, NormalDistThreshold,1, 1,0), 0, 1)
130
+
131
+ // voxel.Color = voxel.Color:Lerp(Color3.new(), Shade)
132
+ // end
133
+
134
+ // return Effects
@@ -0,0 +1,114 @@
1
+ local Effects = { }
2
+
3
+ local function map(x, a_0, a_1, b_0, b_1)
4
+ return b_0 + (b_1 - b_0)*(x - a_0)/(a_1 - a_0)
5
+ end
6
+
7
+ local raycastParams = RaycastParams.new()
8
+ raycastParams.FilterType = Enum.RaycastFilterType.Include
9
+
10
+ local Shatterbox
11
+ Effects._SetRef = function(shatterbox)
12
+ Shatterbox = shatterbox
13
+ end
14
+
15
+ Effects.Default = function(voxel, info)
16
+ voxel:Destroy()
17
+ end
18
+
19
+ Effects.BumpyFloorBreakWalls = function(voxel, info)
20
+ if info.IsAlreadyDebris then return end
21
+
22
+ local O = info.UserData.CastOrigin or info.CuttingPart.CFrame.Position
23
+ local toVoxel = voxel.Position - O
24
+ -- retrieves the untouched original part, to check if the voxel should be considered a floor
25
+ local castTo = Shatterbox:GetOriginalPart(info.DirtyGroupID)
26
+
27
+ raycastParams.FilterDescendantsInstances = { castTo }
28
+
29
+ castTo.Parent = workspace
30
+ local hitResult = workspace:Raycast(O - toVoxel * 0.01, toVoxel * 1.01, raycastParams)
31
+ castTo.Parent = nil
32
+
33
+ if hitResult and hitResult.Normal:FuzzyEq(Vector3.yAxis, 0.01) then -- if the normal is facing up, it is a floor
34
+
35
+ voxel.Position += Vector3.yAxis * (math.random() - 0.5) -- add some random vertical offset to floor voxels
36
+
37
+ else -- everything else becomes a falling voxel using puppeteer
38
+
39
+ Shatterbox:Puppeteer(voxel) -- start controlled replication of "voxel" to clients (tween between replication of CFrames)
40
+
41
+ voxel.AssemblyLinearVelocity = toVoxel.Unit * (math.random() * 25 + 50)
42
+ end
43
+ end
44
+
45
+
46
+ function Effects.Rough (voxel, info)
47
+ if info.IsAlreadyDebris then -- the voxel is already debris
48
+ -- fully contained debris is destroyed
49
+ if not info.IsEdge then
50
+ voxel:Destroy()
51
+ end
52
+
53
+ return
54
+ end
55
+
56
+ local roll = math.random()
57
+
58
+ if info.IsEdge and roll < 0.8 then -- 80% of edge voxels become "edge debris"
59
+ voxel.CFrame *= CFrame.Angles(math.random() * 2 * math.pi, math.random() * 2 * math.pi, math.random() * 2 * math.pi)
60
+ voxel.Size *= math.random() + 1
61
+ return
62
+ end
63
+
64
+ -- a small percentage of voxels are destroyed
65
+ if roll > 0.05 then voxel:Destroy()
66
+ return;
67
+ end
68
+
69
+ -- all other voxels are flying debris
70
+
71
+ voxel.Anchored = false
72
+ voxel.CanCollide = false
73
+ voxel.AssemblyLinearVelocity = (Vector3.new(math.random(), math.random(), math.random()) - Vector3.one * 0.5) * 80
74
+ voxel.AssemblyAngularVelocity = (Vector3.new(math.random(), math.random(), math.random()) - Vector3.one * 0.5) * 20
75
+
76
+ -- remember to destroy the voxel
77
+ game.Debris:AddItem(voxel, 3)
78
+ end
79
+
80
+ -- This effect assumes a spherical hitbox (Shape "Ball").
81
+ -- This is an example show you how to use voxel space to perform calculations that are consistent regardless of the gridsize or orientation of voxels.
82
+ -- The inner 33% of voxels are removed.
83
+ -- Starting at the point where voxels are no longer removed, voxels are black. There is a gradient until around the edges, where the voxels should be their original color.
84
+ function Effects.MapVoxelSpace(voxel, info)
85
+ if info.IsAlreadyDebris then return end
86
+
87
+ -- the radius of the spherical hitbox
88
+ local R = info.CuttingPart.Size * 0.5
89
+
90
+ -- from the center of the sphere, how many voxels away the edge of the sphere is on each axis (in local space)
91
+ local MaxVoxelDist = Shatterbox:VoxelCountVector(voxel, math.min(R.X, R.Y, R.Z)*Vector3.one)
92
+
93
+ -- from the center of the current voxel, how many voxels away the center of the sphere is on each axis (in local space)
94
+ local VoxelDist = Shatterbox:VoxelDistanceVector(voxel, info.CuttingPart.CFrame.Position):Min(MaxVoxelDist)
95
+
96
+ -- The magnitude of this vector is guaranteed to be in the range 0 to close to 1 because spheres hold a consistent magnitude
97
+ local NormalDist = (VoxelDist / MaxVoxelDist).Magnitude
98
+
99
+ -- destroy voxels close to the center (inner 33% by default)
100
+ local NormalDistThreshold = 0.33
101
+
102
+ if NormalDist < NormalDistThreshold then
103
+ voxel:Destroy()
104
+ return
105
+ end
106
+
107
+ -- map and clamp the range [NormalDistThreshold, 1] to the range [1, 0]
108
+ -- So where the voxels begin to appear, they start at black, and where the voxels end, they should be their original color.
109
+ local Shade = math.clamp(map(NormalDist, NormalDistThreshold,1, 1,0), 0, 1)
110
+
111
+ voxel.Color = voxel.Color:Lerp(Color3.new(), Shade)
112
+ end
113
+
114
+ return Effects
@@ -0,0 +1,75 @@
1
+ --#selene: allow(unused_variable)
2
+
3
+ local Settings = {}
4
+
5
+ -- These all-caps named settings cannot be changed at runtime. The rest are okay to do so.
6
+
7
+ Settings.USE_CLIENT_SERVER = true -- Set this to false if you don't want to use client-server functionality (false = fully server side destructions)
8
+
9
+ --Settings.USE_OBJECTCACHE = false -- whether or not to use ObjectCache (not fully implemented)
10
+ -- Settings.CACHE_INITIAL_SIZE = 10000 -- The initial size of the part Caches
11
+
12
+
13
+
14
+ Settings.SkipInstanceCheck = function(i : Instance)
15
+
16
+ -- return true if you want to skip the instance "i"
17
+
18
+ return false
19
+ end
20
+
21
+
22
+
23
+ Settings.NonDivisibleInteraction = "FALL" :: "FALL"|"DESTROY"|"NONE" -- What happens to destructible parts that cannot be divided by Shatterbox
24
+
25
+
26
+
27
+ -- Smooth cleanup settings
28
+
29
+ Settings.UseSmoothCleanup = true -- If false, the CleanupDelay will be ignored and no smooth cleanups will happen at all.
30
+
31
+ Settings.DefaultSmoothCleanupDelay = 60 -- the default smooth cleanup delay if none is provided. Setting the CleanupDelay to 0 or a negative number disables smooth cleanup for that destruction.
32
+
33
+
34
+ -- Part division settings
35
+
36
+ Settings.MaxDivisionsPerFrame = 1000 -- Limits the count of part subdivisions per frame using `Destroy` (1 division = up to 8 new parts before greedy meshing)
37
+
38
+ Settings.DefaultGridSize = 1
39
+
40
+ Settings.MaxOpsPerFrame = 10 -- Limits the number of calls to GetPartsInPart every frame using `Destroy`
41
+
42
+ Settings.UsePriorityQueue = true -- If true, newer operations take precedence over old ones, if false, round-robin processing is used
43
+ Settings.PrioritizeRecentN = 10 -- If using priority queue, the most recent N (default 10) operations are prioritized using round-robin
44
+
45
+
46
+
47
+ -- Greedy meshing settings
48
+
49
+ Settings.UseGreedyMeshing = true
50
+
51
+ Settings.GMTraversalsPerFrame = 10000 -- How many times greedy meshing can iterate through a 3D grid for each worker. Effectively limits the number of visited cells per frame
52
+
53
+ Settings.GMPartCreationsPerFrame = 500 -- How many parts greedy meshing should try to make every frame once the worker is finished traversing. This is a soft-limit, not a hard-limit, for smooth visual display
54
+
55
+ Settings.GMWorkerCount = 5 -- How many greedy meshing workers there are. Each worker performs calculations for a single dirty group before displaying the meshed output
56
+
57
+
58
+
59
+ -- Puppet voxel settings
60
+
61
+ Settings.PuppetMaxCount = 8191 -- HARD CAP 8191
62
+
63
+ Settings.PuppetSleepVelocity = 0.1 -- How slow a puppet has to be moving to not get replicated (and eventually become anchored)
64
+
65
+ Settings.PuppetAnchorTimeout = 1 -- How long a puppet master moving slower than PuppetSleepVelocity has before becoming anchored (seconds)
66
+
67
+ Settings.PuppetReplicationFrequency = 20 -- How frequently puppet CFrames are sent to each client (default 20 fps/Hz target replication frequency)
68
+
69
+ Settings.ClientTweenPuppets = true -- If true, the client will tween between puppet CFrame replications (Client-side only setting)
70
+
71
+ Settings.ClientTweenDistanceLimit = 300 -- Voxels farther than 100 studs away don't get interpolated
72
+
73
+
74
+
75
+ return Settings