Parts getting offset after doing greedy meshing for voxel destruction

I’ve been having issues with my voxel destruction system, I just implemented basic greedy meshing based of of this video: https://www.youtube.com/watch?v=L6P86i5O9iU and made it work in 3d. It has greatly improved the performance but for some reason after creating the parts they are always offset by a small amount from the position of the original part. This issue get worse on bigger parts. I don’t know what’s causing it but if someone notices an issue in my code that is causing this please let me know and your help would be greatly appreciated.

External Media

Here is the code for my module script:

local Destruction = {}

--Functions
local function CalculateBlockCount(vec, op)
	return Vector3.new(op(vec.X), op(vec.Y), op(vec.Z))
end

function GetClosestPointOnBox(collider, boxCFrame, boxSize)
	local transform = boxCFrame:PointToObjectSpace(collider.Position)
	local halfSize = boxSize/2
	local position = boxCFrame * Vector3.new(math.clamp(transform.X, -halfSize.X, halfSize.X), math.clamp(transform.Y, -halfSize.Y, halfSize.Y), math.clamp(transform.Y, -halfSize.Y, halfSize.Y))
	return position
end

function Destruction:SplitPart(targetPart: BasePart, collider: BasePart, voxelSize: number)
	local regionPos = {}
	
	local boundParams = OverlapParams.new()
	boundParams.FilterDescendantsInstances = {targetPart}
	boundParams.FilterType = Enum.RaycastFilterType.Exclude
	
	local blockCount = CalculateBlockCount(targetPart.Size, function(l) return math.floor(l / voxelSize) end)
	local blockSize = targetPart.Size / blockCount

	for x = 1, blockCount.X do
		regionPos[x] = {}
		for y = 1, blockCount.Y do
			regionPos[x][y] = {}
			for z = 1, blockCount.Z do
				local offset = (targetPart.Size + blockSize) / 2
				local cframe = targetPart.CFrame:ToWorldSpace(CFrame.new(Vector3.new(x,y,z) * blockSize - offset))
				
				local closestPointOnBox = GetClosestPointOnBox(collider, cframe, blockSize)
				local distance = (collider.Position - closestPointOnBox).Magnitude
				
				if distance > collider.Size.Y/2 then	
					regionPos[x][y][z] = {["X"] = 1, ["Y"] = 1, ["Z"] = 1, ["Position"] = cframe.Position}
				end
			end
		end
	end
	
	for x, row in pairs(regionPos) do
		for y, column in pairs(row) do
			for z, size in pairs(column) do
				if not regionPos[x][y][z] then continue end
				if regionPos[x][y][z - 1] == nil then continue end
				regionPos[x][y][z].Z += regionPos[x][y][z - 1].Z
				regionPos[x][y][z - 1] = nil
			end
		end
	end

	for x = 2, blockCount.X do
		for y = 1, blockCount.Y do
			for z, size in pairs(regionPos[x]) do
				if not regionPos[x][y][z] then continue end
				if regionPos[x - 1][y][z] == nil then continue end
				if size.Z ~= regionPos[x - 1][y][z].Z then continue end
				size.X += regionPos[x - 1][y][z].X
				regionPos[x - 1][y][z] = nil
			end
		end
	end

	for x, row in pairs(regionPos) do
		for y = 2, blockCount.Y do
			for z, size in pairs(regionPos[x][y]) do
				if not regionPos[x][y][z] then continue end
				if regionPos[x][y - 1][z] == nil then continue end
				if size.Z ~= regionPos[x][y - 1][z].Z then continue end
				if size.X ~= regionPos[x][y - 1][z].X then continue end
				size.Y += regionPos[x][y - 1][z].Y
				regionPos[x][y - 1][z] = nil
			end
		end
	end

	for x, row in pairs(regionPos) do
		for y, column in pairs(row) do
			for z, size in pairs(column) do
				local position = size.Position
				local part = Instance.new("Part")
				part.Anchored = true
				part.Size = Vector3.new(size.X, size.Y, size.Z) * blockSize
				part.Position = Vector3.new(position.X - size.X / 2, position.Y - size.Y / 2, position.Z - size.Z / 2) * blockSize
				part.Color = targetPart.Color
				part.Material = targetPart.Material
				part.TopSurface = targetPart.TopSurface
				part.BottomSurface = targetPart.BottomSurface
				part.Parent = workspace
			end
		end
	end
	targetPart:Destroy()
end

return Destruction

And this is the code for my local script:

local UIS = game:GetService("UserInputService")
local destructionModuleV2 = require(script:WaitForChild("DestructionV2"))

local rayParams = RaycastParams.new()
rayParams.FilterDescendantsInstances = {game.Players.LocalPlayer.Character}
rayParams.FilterType = Enum.RaycastFilterType.Exclude

local camera = workspace.CurrentCamera
local voxelSize = 1

UIS.InputBegan:Connect(function(input, gd)
	if input.KeyCode == Enum.KeyCode.F then
		local ray = workspace:Raycast(camera.CFrame.Position, camera.CFrame.LookVector * 1000, rayParams)
		if ray and ray.Instance then
			local part = Instance.new("Part")
			part.Anchored = true
			part.Transparency = .5
			part.BrickColor = BrickColor.Red()
			part.CanCollide = false
			part.Position = ray.Position
			part.Size = Vector3.new(4, 4, 4) * 2
			part.Shape = Enum.PartType.Ball
			part.Parent = workspace
			
			local startTick = tick()
			
			local partsInCollider = workspace:GetPartsInPart(part)
			for i, p in pairs(partsInCollider) do
				destructionModuleV2:SplitPart(p, part, voxelSize)
			end
			part:Destroy()
			
			print(tick() - startTick, "Total time")
		end
	end
end)

Ended up figuring out that I just needed to offset the parts by half their size to each axis when setting the position. Currently only works for a voxel size of 1 but that’s what I want it to work with so It doesn’t matter that much.

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