Hey everyone!
For the past 2 months I have been working on a bit of a passion project: a non-destructive voxel destruction engine.
I may turn this into an open-sourced project depending on how much demand there is. It will potentially be released very soon, depending on how long the remaining issues take for me to fix & how long it takes me to de-bloat the code and write documentation.
As a new programmer (this is my first post on the forum ever!) and CS student this has been a huge learning experience for me and Iâm excited to finally share it and get some feedback.
Capabilities & Features
Q: Now you might be wondering, how can a destruction engine be non-destructive?
A: It involves enabling a âslicingâ behavior which actually does not destroy any geometry.
Here is an example:
As you can see, in this example the structure actually doesnât lose any geometry, if you were to puzzle together the 2 remaining âchunksâ after this operation, they would fit together perfectly to form the original structure.
Something cool about this is that you can continue to slice the product of ANY OTHER slicing event:
(I use a command to give velocity to every chunk after slicing it here)
It allows for essentially infinite subsequent cuts as long as there is still something to cut. If you try to cut a chunk which is the same size as a voxel, it cannot be cut, so technically you can cut everything up until your chosen voxel size.
Despite my emphasis on this being non-destructive, it DOES have destructive capabilities:
The destructive capabilities are somewhat unoptimized as they werenât my main focus for most of development. They use mostly the same logic as for non-destructive cuts.
All cuts properly preserve color and other visual properties, and happen in the world space:
Note: currently the only thing you can actually cut are rectangular baseparts. You cannot cut meshparts, or any other shape of basepart.All cuts accept an optional parameter for preferred voxel size. This is because using larger voxels results in vastly improved performance and speed, and thus faster feedback, especially for complicated cuts:
Note: if an object/structure cannot be evenly divided into cubes of your voxel size, it will choose the next-highest working option. This means that objects with a prime number size currently cause lower performance on average because they will always default to a voxel size that encompasses the entire object, or 1 (the highest integer voxel resolution) This is something I am hoping to fix soon as well, by using âcut fidelityâ over voxel size in drastic cases.This project uses a comprehensive connectivity analysis module to do island detection. If a part has no logical path to a tagged baseplate, it will be unanchored alongside anything else that touches it:
As you can see this even works if some of the parts belong to a model and some do not. These objects require no prep in studio beyond giving them the destructible tag. If a part should not be floating, it simply will not float.Another cool thing I made this capable of doing is converting an entire map into a ready to cut format. This involves slicing up an entire map into 32x32x32 stud âcellsâ as a sort of shortcut to narrow-band voxelization:
Note: this raises part count potentially by a lot, and is preserved on dynamic, unanchored chunks for the same reasons. This entire âcellâ paradigm is a substitute for true narrow band voxelization, if I was using true narrow band voxelization, I would not use this paradigm.
Known Bottlenecks & limitations
The voxelization method I use (dense voxelization) is terribly inefficient but may be required to allow for cutting objects which have tumbled to an angle. Additionally, this happens in a parallel thread via actors, and so it has no effect on the main thread (besides possibly taking so long that slicing jobs get piled up in the queue.)
The connectivity analysis methods are also slightly inefficient, especially for very complex cuts. This inefficiency though is not something I have been able to reduce without reducing the capabilities of the entire engine.
Simply parenting the number of parts required for some complex cuts, and welding them all together can take a very long time, and cause quite a bit of main thread hitch. This is something that I have done my best to reduce via pooling, and greedy meshing (of course). This is something I have identified as a roblox problem rather than one i can fix.
Obviously if you continue to spam cut stuff over time, the physics engine will begin to lag. The only way to prevent this is by reducing the number of parts (in my knowledge) which also involves using a larger voxel size. Some ways to get around this are to manage the lifecycle of debris which I can and do track but do not manage yet.
Despite the use of actors, the main thread is not always crystal clear because of bottleneck #3. This degrades the usefulness of the entire project a bit for use in real games, but is again avoidable by limiting the resolution and scale at which destruction occurs.
All builds must be done with a minimum part size (in any axis) of your default voxel size. I am using voxel size 1 as my default voxel size because anything lower gets quite a bit laggier. (for example, since my default voxel size is 1, the thinnest part in any build I make MUST be no thinner than 1 stud.)
Streaming needs to be DISABLED. This is a weird one I donât quite understand but I do know that if streaming is enabled, there is a terrible flicker effect when you slice anything.
Hereâs a video of me doing a long stress test:
I will be constantly applying velocity to objects to show how the engine performs in scenarios where there are lots of moving parts.
As you can see:
-
The physics engine certainly does not like having to deal with thousands of dynamic parts (especially when I am manually applying velocity to them)
-
Main thread hitches a bit when cutting very large objects, especially when it needs to default to a voxel size of 1. (causing ping spikes)
-
Overall the integrity of the geometry stays consistent. There are no malformed chunks being made, no seams between objects, for the most part the connectivity analysis is successful.
-
Cuts continue working in very high stress environments at a somewhat reasonable latency. Only slowing down if there is a high number of other cuts waiting to be pushed through the queue.
-
There is a little bit of visible hitch when cutting objects which is also something I am thinking about how to fix.
Notes:
-
This DOES support multiplayer but due to the nature of the job queue, if multiple players are spamming cut events, the job queue will easily be bogged down. Technically the game would get more VM CPU threads if it had more active players (I think?) which would easily boost the throughput.
-
The specific âattacksâ you see me using in the videos are a part of a demo module I made trying to demonstrate the extensibility of the core engine. This includes a projectile service I made which comes with itâs own array of performance benefits, and allows for piercing planes (like what I am using in the video.)
Test Game Link:
Not the most polished version yet, but all you really need to know is the commands/inputs.
Hotkeys & Commands list
Hotkey commands:
Q on your keyboard- Vslash, a vertical piercing slash you can see me using frequently in the video above. This slash has a hitbox equal to the size of the projectile you see. When it collides with anything, it will slash through it with an infinite oriented plane.
E on your keyboard- Hslash, a horizontal piercing slash you can also see me using in the video above. Acts similarly to Vslash, the only distinction being it has a different plane orientation.
F on your keyboard- âForce Pushâ just applies velocity to all objects on the map. Very laggy if you spam it because of the physics engine, but required in some cases to see the cut you just made (because the geometry may be so flush and stable that it does not immediately appear visible)Chat commands (optional parameters):
/spherecut [radius = integer] [preferred voxel size = integer] [generate breakaway? = boolean]
The last parameter determines if you will cut or âdestroyâ the intersected voxels. Example usage: /spherecut 25 10 False
/aoeplane [preferred voxel size = integer] [generate breakaway? = boolean]
A big, flat 200x200 plane. Not recommended to set generate breakaway to false as it will just make everything above the flat plane vanish. Example usage: /aoeplane 5 True
/cylindercut [length = integer] [radius = integer] [preferred voxel size = integer] [generate breakaway? = boolean].
A cylinder shaped cut. Example usage: /cylindercut 10 15 5 True
/pillcut [length = integer] [radius = integer] [preferred voxel size = integer] [generate breakaway? boolean]
A cylinder shaped cut with a sphere on each end. Used to test the union function of my SDF generator. Example usage: /pillcut 10 15 5 True
/disable_destruction & /enable_destruction
Locks all assemblies, making them unable to be cut, and vice versa.
I'd love to hear your feedback!
Any feedback at all is appreciated, but Iâm especially curious to hear your thoughts on the following:
- Do you find the destruction satisfying?
- What kind of gameplay mechanics could you see this being used for?
- If you were to use this system in your own game, what are some features you do not see in the demo which you would need?
- Have you ever seen another non-destructive voxel destruction engine on roblox?
- If you tried the demo, how did it perform on your device? Especially interested in lower-end devices, as streaming is disabled.
Thanks for taking the time to check out my project, I cant wait to hear what you think!