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
Hell, even the representation of the data structure is quite similar to your image, haha
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.
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.
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.
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
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)
That works! Thank you so much! I’ve been struggling on finding resources for voxel related scripts, so this is incredibly helpful.
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.
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.
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.
This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.