Splitting a part into cubes

Continuing from this post, I am looking for a way to turn any part of any shape or size, into evenly shaped cubes which fit along the size of the original part.

Are the subcubes the same size every time regardless of the original size of the cube? Basically does both a 5x5x5 cube and 50x50x50 cube generate 1x1x1 sized subcubes?

No, the size of the cubes should be relative to the original part, but they should all be the same size. And since this likely isn’t possible, then maybe have some filler parts to fill in the gaps, since its probably unlikely that you could have evenly sized cubes fit every single part.

This makes me think of an octree, OP. It’s a data structure that’s the 3D analogue of the quadtree, which subdivides a grid into denser portions in areas where data is more cluttered. Kind of analogous to “data” being the site of an explosion/destruction as the action would happen there.

Perhaps that data structure could help you out to partition the part into cubes of increasing density? That way, explosions force a fine decomposition of nearby voxels while keeping other areas of the cube coarsely divided to limit physics simulations

image

Hell, even the representation of the data structure is quite similar to your image, haha

4 Likes

I have heard of them, and I have researched them a little prior to posting this. But as far as I can look, I can’t find any decent explanation on octrees or how to use them, at least not in the devforum.

1 Like

I’d say you could split the part into 4s, using the longest side for the standard division.

local targetPart = ... -- target part
local size = math.max(targetPart.Size.X, targetPart.Size.Y, targetPart.Size.Z)
print("Size of each subcube is", Vector.new(size, size, size))

The position is a bit more complicated

local N = 4 -- # of cubes used for the split

local function lerp(a, b, alpha) 
	-- simple lerp function to get an in-between number of a and b
	-- when alpha = 0, return a
	-- when alpha = 1, return b
	-- when alpha = 0.5, return a/2 + b/2
	return a * (1 - alpha) + b * (alpha)
end

for x = 1,N do
	for y = 1,N do
		for z = 1,N do
			local xPos = lerp(0, 1, (x-1)/(N-1)) * (targetPart.Size.X - size) / (targetPart.Size.X) + size / 2
			local yPos = lerp(0, 1, (y-1)/(N-1)) * (targetPart.Size.Y - size) / (targetPart.Size.Y) + size / 2
			local zPos = lerp(0, 1, (z-1)/(N-1)) * (targetPart.Size.Z - size) / (targetPart.Size.Z) + size / 2
			local position = targetPart.Position - targetPart.Size/2 + Vector3.new(xPos, yPos, zPos)
		end
	end
end

There’s a lot going on but essentially all of the fancy calculations is just to figure out where to place the subcubes at.

image
With N = 4, we need to generate numbers 0, 0.333, 0.667, 1.0, so that’s why I used the lerp function. We then shrink the number enough for there to be size/2 slack on each side and center it.

1 Like

Thank you, and I may have not interpreted your code correctly, but this is what I get when running that:

Sorry about that. I should’ve tested it in-game. I messed up the proper sizing of things. This is the fix. I tested it in-game.

local targetPart = workspace.Part -- target part
local size = math.max(targetPart.Size.X, targetPart.Size.Y, targetPart.Size.Z)

local N = 4 -- # of cubes used for the split
size = size / N -- size of each cube is the largest length divided by # of cubes per dimension
print("Size of each subcube is", Vector3.new(size, size, size))

local function lerp(a, b, alpha) 
	-- simple lerp function to get an in-between number of a and b
	-- when alpha = 0, return a
	-- when alpha = 1, return b
	-- when alpha = 0.5, return a/2 + b/2
	return a * (1 - alpha) + b * (alpha)
end

for x = 1,N do
	for y = 1,N do
		for z = 1,N do
			local xPos = lerp(0, 1, (x-1)/(N-1)) * (targetPart.Size.X - size) + size / 2
			local yPos = lerp(0, 1, (y-1)/(N-1)) * (targetPart.Size.Y - size) + size / 2
			local zPos = lerp(0, 1, (z-1)/(N-1)) * (targetPart.Size.Z - size) + size / 2
			local position = targetPart.Position - targetPart.Size/2 + Vector3.new(xPos, yPos, zPos)
			local size = Vector3.new(size, size, size)
			
			local cube = targetPart:Clone()
			--cube.Position = position
			cube.CFrame = (targetPart.CFrame * CFrame.new(-targetPart.Size/2)) * CFrame.new(xPos, yPos, zPos)
			cube.Size = size
			cube.Parent = workspace
		end
	end
end
1 Like

That’s nearly perfect, just the orientation of the cubes doesn’t match up. (and thank you once again by the way)

Change

cube.Position = position

to

cube.CFrame = (targetPart.CFrame * CFrame.new(-targetPart.Size/2)) * CFrame.new(xPos, yPos, zPos)
1 Like

That works! Thank you so much! I’ve been struggling on finding resources for voxel related scripts, so this is incredibly helpful.

1 Like

Apologies for bothering you after having already marked your response as a solution, but I am noticing that if the difference between the sizes of the axis’ of the part is too great, then the cubes will end up being bigger than the original part. You can see this with thin and long parts:

Their code appears to be for splitting a cube into cubes, not a cuboid into cubes.

2 Likes

Yeah, I didnt realize this until now but when it comes to unevenly shaped cuboids, its impossible to divide them into evenly shaped cubes. The one workaround I could think of is just changing one of the axis’ of the cubes so that they fit into the original part. But the math for positioning these is a little complicated for me so im gonna experiment a bit

For rectangular cuboids, you cannot use this algorithm when the smallest side is less than the largest side / N.

Condition 1: Smallest >= Largest / N

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

You can use a larger subdivision number N to fix this. Note that the part count will be higher.
The minimum N can be derived from condition 1:

Condition 2: N >= Largest / Smallest

local function getMinimumN(part)
	-- return the smallest N that makes the part valid for cube division
	local largest = math.max(targetPart.Size.X, targetPart.Size.Y, targetPart.Size.Z)
	local smallest = math.min(targetPart.Size.X, targetPart.Size.Y, targetPart.Size.Z)
	
	-- N >= largest / smallest
	-- watch the part count! the # of parts will be N*N*N
	local N = math.ceil(largest / smallest)
	return N
end

Note that when Smallest == Largest / N, all subcubes overlap into the same space, which isn’t efficient. You can replace the >= check with > in the partCanSubdivide function if you don’t want this happening.

Another option would be for you to split the part into rectangular cuboids instead of pure cubes. This would allow the part to be splitable without generating a huge part count, especially for paper thin objects. Basically do what you didn’t want to do before.
image

I set up a similar system in my games. If N gets too large I simply split the part into rectangular cuboids rather than cubes, which guarantees 8 new parts instead of N x N x N.

Yeah the code should be run on cubes to not get any side effects. But OP’s original photo showed a cuboid split into cubes, with the cubes overlapping.

1 Like

I figured it out on the original post, so check that out for details

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