How to do optimized Voxel Destruction?

Thank you for the excellent explanation, I was able to use this to make something much more optimized, though still a bit flawed.


I’ve also been trying to incorporate greedy meshing into it, but it seems kind of redundant, as the majority of the lag is caused by instancing the parts rather than the parts existing in the workspace. And you would greedy mesh after the parts have been divided, so there is really no point in greedy meshing here as far as I’m aware.

One other thing, with parts that are uneven, when you divide them, they end up not looking like cubes, but just smaller versions of the original part. Which is obvious. Instead of just dividing the part, a better idea would be to divide the part into cubic shapes. Now how you’d go about doing this, I have no idea. But if anyone figures this out please let me know.

2 Likes

As for the odd part size, you could try finding the biggest number that can be divided into all 3 axes of a part. Effectively turning it into a bunch of cubes. However, I’m not sure how you could implement both octrees and this system. Maybe octrees aren’t necessary? Also, the smaller the voxels are the less noticeable this is.

I’m not so sure about the greedy meshing situation I’d imagine the fewer parts created the less laggy but it puts a lot of strain just to greedy mesh the parts. But if you plan on adding welding greedy meshing before you weld will reduce the strain on welding. Just depends it seems.

1 Like

keep dividing touching parts until it dimensions on x and z axis is equal to one or less ?
2x2 is probably better though
(ignoring y axis because it doesn’t have much problems with odd numbers)

Just use quadtree (i mean octree)partitioning to a small size, you’re not going to get perfect dimensions with any method unless you want to voxelize the whole part which would still give you the same effect just laggier. People aren’t gonna really notice it

edit: here is an example of octrees Dynamic Octree System - #9 by plasma_node

The question is: Why even destroy them? There’s a high chance you’ll regenerate those, so you should just turn off CanCollide and CanQuery, and set Transparency to 1, that allows u to detect if a part has been hit (by checking it’s transparency or any of the 3 properties), and allows easy regeneration; just setting back the values to their default.

phrehaps you should change this module so if the object that is destroyed is bigger the parts also become bigger and less.

I may be wrong about this but, from the examples i see on the module you linked, octree partitioning is basically the exact same as what i was already doing, which was diving the part into smaller parts, and continuously doing so for any parts touching the hit box until they reach a minimum size. In which case, i run into the same problem: Dividing uneven cuboids will result in rectangular voxels.

There’s nothing you can do for rectangular voxels (that i know of) except for dividing more so they are smaller, I’d reccomend avoiding non-cubes if you can and make sure the x and z axis are equal numbers.

My bad, I just realized the problem. https://www.youtube.com/watch?v=8AdcPD50aTQ

In this video you see they cut the rectangle in half and then make their collums. You would basically do what he does in the video, cut it in half and then divide it into collumns but in 3d. This would be like grid voxelization but on a larger scale (minimum size for an axis is x, y, z / 3, 2, 3 ), Then for the parts inside the hitbox you would do octtree partitioning. Also you could detect if its not a cube so you wouldn’t have to do this for every single one. So optimally (i think…) you would have a shape made of 3x2x3 cubes. This is a very quickly thought idea, but try it out and see if it works!

NEVERMIND I DON’T KNOW !!! IF YOU WANT THIS TO WORK THEN YOU CAN’T HAVE UNIFORM SIZES AND THE BEST OPTION IS TO JUST KEEP DIVIDING TO SMALL NUMBER

Hi again,

I haven’t played the game, does the same effect work on the trees? They seem to be rotated so it would be interesting to see if the voxel algorithms work on it.

There’s a couple of suggestions already on subdividing cubes so I’ll pitch a different idea.

You can use a union operations to get the look you’re after too.

Demo file:
UnionDestruction.rbxl (313.8 KB)

The operation is very slow on multiple parts so you’d want to use it only on 2-3 parts at most at a time. You can achieve this by unioning the entire building into 1 part. Another separate union should be done for all the glass objects if you want to preserve their transparency effect.

Added note (Parallel Processing): Unfortunately the union operations aren’t thread safe to use so we cannot speed them up by splitting it up into threads.

Lastly, that “Voxel-like” look to the explosion could be mimicked by creating a union that’s shaped like it. Although this is very tedious unfortunately.
image

Demo file:
UnionDestruction.rbxl (313.8 KB)

2 Likes

This was actually the response that helped me figure it out.
I’ve basically perfected the system that I had tried to make from the beginning:


To explain a bit, I’m using quadtree partitioning with a function sourced from this post
But before I divide the part into four, I use this function to check if the part is rectangular:

local function partCanSubdivide(part)

	-- can we run the subcubes algorithm on this part w/o artifacts?
	local largest = math.max(part.Size.X, part.Size.Y, part.Size.Z)
	local smallest = math.min(part.Size.Y, part.Size.Z)

	if smallest == part.Size.X then
		smallest = math.min(part.Size.Y, part.Size.Z)
	elseif smallest == part.Size.Y then
		smallest = math.min(part.Size.X, part.Size.Z)
	elseif smallest == part.Size.Z then
		smallest = math.min(part.Size.X, part.Size.Y)
	end

	print(largest)
	print(smallest * 1.5)
	return largest >= 1.5 * smallest
--returns true if part is rectangular. 
--Part is rectangular if the largest axis is at least 1.5x bigger than the smallest axis
end

Then I have a separate function to check what the longest axis of the part is:

local function getLargestAxis(part:Part)
	return math.max(part.Size.X, part.Size.Y, part.Size.Z)
end

Then, if it is rectangular, I cut the part in half to turn it into squares with this function:

local function CutPartinHalf(block : Part)
	local partTable = {}
	local bipolarVectorSet = {}


	local X = block.Size.X
	local Y = block.Size.Y
	local Z = block.Size.Z

	if getLargestAxis(block) == X then
		X /= 2

		bipolarVectorSet = {
			Vector3.new(1,0,0),
			Vector3.new(-1,0,0),
		}

	elseif getLargestAxis(block) == Y then
		Y/=2

		bipolarVectorSet = {
			Vector3.new(0,1,0),
			Vector3.new(0,-1,0),
		}

	elseif getLargestAxis(block) == Z then
		Z/=2

		bipolarVectorSet = {
			Vector3.new(0,0,1),
			Vector3.new(0,0,-1),
		}

	end




	local halfSize = Vector3.new(X,Y,Z)

	for _, offsetVector in pairs(bipolarVectorSet) do

		local clone = block:Clone()
		clone.Parent = workspace
		clone.Size = halfSize
		clone.CFrame += block.CFrame:VectorToWorldSpace((halfSize / 2.0) * offsetVector)
		table.insert(partTable,clone)
	end
	block:Destroy()
	return partTable
end

And otherwise if the part isn’t rectangular (meaning it’s a cube), I just use quadtree partitioning to divide the part into four, resulting in evenly shaped cubes across the entire original part.

-Which is what I would say if you could divide every possible part into perfect cubes. Because that isn’t possible, my system turns cuboids into squares in the event that fitting perfect cubes isn’t possible, and will make the smallest axis of the original part match up with the divided cubes.

-In simpler terms, if I have a thin wall and divide it with my system, since cubes can’t be perfectly made, it will instead make squares that match the thickness of the wall

And as for actually writing the function that destroys the wall based on a hitbox, I’m gonna keep that to myself, BUT it’s pretty simple. Just create a part of any shape (your ‘hitbox’), and use a loop or run service to constantly check for parts touching your hitbox. Then, continuously divide the parts that are touching your hitbox until they reach a minimum size.

Now obviously this isn’t super performant with larger destruction, but I’m just gonna chalk that up to a limitation of roblox.

and @treebee63, I ended up not using the cube division script you gave me, so sorry for the trouble with that.

19 Likes

Glad I could help with my ragtag ideas

Edit: Also for those windows on the tower I would reccomend making each one a giant part that is thinner than the frame so it doesn’t have to do as much calculations

1 Like

Question: Why do you check for the smallest axis multiple times? I don’t understand why not just do a math.min and max

So im gonna assume you’re referring to the function that checks if a part is rectangular.
Basically, the smallest axis should be relative to what axis the part is longest in. Lets say for example, you have a rectangular part and the smallest axis is the Z axis, in which case you shouldn’t be comparing the Z axis to the longest axis, as it would return if the part is rectangular relative to the Z axis. Instead, we ignore the smallest axis, and instead compare the second smallest axis to the largest axis.

To put it simply, when you have a super thin wall, then its gonna be comparing the depth of the wall to the longest axis rather than the width and the height, since the depth is the smallest axis in this case. So we instead make it ignore the depth so that just the width and height are compared.

The reason its written this way is because if you were just comparing the smallest and largest axis’, in some cases the function would be returning true for parts that aren’t rectangular.

Ohh but then whats the point of the If smallest == x then min? I understand the other values but not the x one

Edit: It must be annoying to build maps with this lol

Also by relative what do you mean?
Oh I think I understand (keyword think) You use the 2nd smallest value since the 1st smallest will always be irrelevant to the actual shape. In this case I would probably call the second smallest the definition since it defines the rectangle.

By relative I meant the ‘smallest’ axis should be perpendicular to the largest axis. Its kind of hard to explain this without images, but im not at my computer right now. So the point is to ignore whatever the actual smallest axis is, and use the second smallest axis instead. And yeah I probably could’ve wrote the code for this a bit better, as I understand the confusion.

I’ve created an open source module that combines everything taken from this thread. See it here

5 Likes

so just to be clear this work on just one big part or you need to create multiple parts?

This works on any size of part, big or small

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.