Why it floating?

I don’t know why it floating I check in the server and everything should fall correctly but it didn’t idk what is going on

code (there a lot of stuff that I didn’t use idk and messy af)

local Physic = {}
local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")

local Blink = require(game.ReplicatedStorage.Blink.Server)
local Config = require(script.Parent.Config)
local MsConvert = require(script.Parent.MSConvert)
local WeldCache = require(script.WeldCache)
local Queuer = require(script.Parent.Queue)
local SAT = require(script.Parent.SAT)
local Cache = WeldCache.new(10000, 100)

local Visited = {}
local WeldQueue = {}
local WeldLookup = {}
local IDS = {}
local DeleteTable = {}
local ID = 0
local FallID = 0

local function assignId(a: BasePart)
	local id = IDS[a]

	if not id then
		ID += 1
		id = ID
		IDS[a] = id
	end

	return id
end

local function getKey(a: BasePart, b: BasePart): string
	if a:HasTag("Anchored") and not b:HasTag("Anchored") then
		a, b = b, a
	end
	
	local aKey, bKey = assignId(a), assignId(b)

	if aKey < bKey then
		return aKey.."_"..bKey
	else
		return bKey.."_"..aKey
	end
end

local function hasRequiredTag(part)
	return part:HasTag("Anchored") or part:HasTag(Config.tag)
end

local function CanConnect(a : BasePart, b : BasePart)	
	if hasRequiredTag(a) ~= hasRequiredTag(b) then
		return false
	end
	
	if a:HasTag("NoPhysics") ~= b:HasTag("NoPhysics") then
		return false
	end

	local aFall = a:GetAttribute("_FALLID")
	local bFall = b:GetAttribute("_FALLID")

	if aFall or bFall then
		if aFall ~= bFall then
			return false
		end
	end

	return true
end

local function tblToLookup(tbl)
	local lookup = {}
	for _, value in ipairs(tbl) do
		lookup[value] = true
	end
	return lookup
end

local function GetConnected(Part : BasePart,IgnoreParts : {BasePart},IsPreWelding : boolean)
	local Start = os.clock()
	
	local Optimize = tblToLookup(IgnoreParts)
	
	local Queue = {Part}
	local Results = {} -- :P
	local IsTouchingGround = false
	
	while #Queue > 0 do
		local currentPart = table.remove(Queue, 1)
		
		if not Visited[currentPart] and not Optimize[currentPart] then
			Visited[currentPart] = true
			table.insert(Results,currentPart)
			
			if currentPart:HasTag("Anchored") or currentPart:HasTag("NoPhysics") then
				IsTouchingGround = true
			end
			
			local neighbors = workspace:GetPartBoundsInBox(currentPart.CFrame, currentPart.Size + vector.create(0.001,0.001,0.001))
			
			for _,neighbor in pairs(neighbors) do
				if neighbor ~= currentPart and not Visited[neighbor] and not Optimize[neighbor]  then
					if CanConnect(currentPart,neighbor) then
						local Key = getKey(currentPart,neighbor)
						
						if neighbor:IsA("Part") then
							neighbor.Shape = Enum.PartType.Block
						end
						
						if neighbor:HasTag("Anchored") or neighbor:HasTag("NoPhysics") then
							IsTouchingGround = true
						end

						if IsPreWelding then
							table.insert(Queue,neighbor)
						else
							if not neighbor:HasTag("Anchored") then
								table.insert(Queue,neighbor)
							end
						end
						
						if not WeldLookup[Key] then
							WeldLookup[Key] = true
							table.insert(WeldQueue,{currentPart,neighbor})
						end
					end
				end
			end
		end
	end
	
	--print("[DEBUG] Search Time :",MsConvert.Convert(Start))
	return Results,IsTouchingGround
end

local function getLayers(parts, layerThreshold)
	layerThreshold = layerThreshold or 2
	local layers = {}
	local currentLayer = {}
	local lastY = nil

	for _, part in ipairs(parts) do
		if part:HasTag("Anchored") then continue end
		
		if not lastY or part.Position.Y - lastY > layerThreshold then
			if #currentLayer > 0 then
				table.insert(layers, currentLayer)
			end
			currentLayer = {}
		end
		table.insert(currentLayer, part)
		lastY = part.Position.Y
	end

	if #currentLayer > 0 then
		table.insert(layers, currentLayer)
	end

	return layers
end

local RunService = game:GetService("RunService")

local function CheckIntegrity(parts : {BasePart}, layerThreshold : number)
	layerThreshold = layerThreshold or 2

	local layers = getLayers(parts, layerThreshold)
	if not layers[1] or #layers[1] == 0 then
		warn("No bottom layer found")
		return false
	end
	
	local failedLayers = {}
	local totalMass = 0

	for i = 1, #layers - 1 do
		local lower = layers[i]
		local upper = layers[i + 1]

		local lowerMass, upperMass = 0, 0
		for _, p in ipairs(lower) do
			if p:IsA("BasePart") then
				lowerMass += p:GetMass() + totalMass
			end
		end
		
		totalMass += lowerMass
		
		for _, p in ipairs(upper) do
			if p:IsA("BasePart") then
				upperMass += p:GetMass()
			end
		end

		if lowerMass < upperMass then
			print(("Layer %d failed! Lower mass: %.2f < Upper mass: %.2f"):format(i, lowerMass, upperMass))
			table.insert(failedLayers,lower)
		end
	end

	return true,failedLayers
end

local function getShape(visualize)
	local destroyableParts = CollectionService:GetTagged("Destroyable")
	local visited = {}
	local clusters = {}

	local function findCluster(startPart)
		local cluster = {}
		local stack = {startPart}

		while #stack > 0 do
			local current = table.remove(stack)
			if not visited[current] then
				visited[current] = true
				table.insert(cluster, current)
				
				local touching = workspace:GetPartBoundsInBox(current.CFrame, current.Size + vector.create(0.001,0.001,0.001))

				for _, otherPart in ipairs(touching) do
					if not visited[otherPart] and otherPart ~= current then
						table.insert(stack, otherPart)
					end
				end
			end
		end
		return cluster
	end

	local function checkTouchGround(cluster)
		for _, clusterPart in ipairs(cluster) do
			if clusterPart:GetAttribute("_FALLID") then
				return false
			end
		end
		
		local anchoredParts = CollectionService:GetTagged("Anchored")
		local params = OverlapParams.new()
		params.FilterDescendantsInstances = {anchoredParts}
		params.FilterType = Enum.RaycastFilterType.Include
		
		local center,size = getBoundingBox(cluster)
		
		local parts = workspace:GetPartBoundsInBox(center,size,params)
		
		if #parts > 0 then
			return true
		end

		return false
	end

	for _, part in ipairs(destroyableParts) do
		if not visited[part] then
			local cluster = findCluster(part)
			if #cluster > 0 then
				local firstPart = cluster[1]
				local id = firstPart:GetAttribute("_CLUSTERID")

				if not id then
					id = tick() + math.random()
					for _, p in ipairs(cluster) do
						p:SetAttribute("_CLUSTERID", id)
					end
				end

				local clusterData = {
					parts = cluster,
					touchGround = checkTouchGround(cluster),
					id = id,
				}
				table.insert(clusters, clusterData)
			end
		end
	end

	if visualize then
		for i, clusterData in ipairs(clusters) do
			local model = Instance.new("Model")
			model.Name = "Cluster_" .. i .. (clusterData.touchGround and "_Grounded" or "_Floating")
			model.Parent = workspace

			local highlight = Instance.new("Highlight")
			highlight.FillColor = clusterData.touchGround and Color3.new(0, 1, 0) or Color3.new(1, 0, 0)
			highlight.OutlineColor = Color3.new(1, 1, 1)
			highlight.Parent = model

			for _, part in ipairs(clusterData.parts) do
				part.Parent = model
			end
		end
	end

	return clusters
end

local function extractPartData(parts)
	local data = table.create(#parts)
	for i, part in ipairs(parts) do
		data[i] = {
			CFrame = part.CFrame,
			Size = part.Size,
		}
	end
	return data
end

function getBoundingBox(parts)
	if #parts == 0 then return nil end
	
	if #parts == 1 then
		local p = parts[1]
		return p.CFrame, p.Size
	end

	local data = extractPartData(parts)

	task.desynchronize()

	local centerPos = Vector3.zero
	for i = 1, #data do
		centerPos += data[i].CFrame.Position
	end
	centerPos /= #data

	local bestCF = CFrame.new(centerPos)
	local bestVolume = math.huge

	for i = 1, #data do
		local partCF = data[i].CFrame
		local testCF = CFrame.new(centerPos) * (partCF - partCF.Position)

		local minX, minY, minZ = math.huge, math.huge, math.huge
		local maxX, maxY, maxZ = -math.huge, -math.huge, -math.huge

		for j = 1, #data do
			local p = data[j]
			local relCF = testCF:Inverse() * p.CFrame
			local size = p.Size

			local sx, sy, sz = size.X / 2, size.Y / 2, size.Z / 2
			for _, corner in ipairs({
				Vector3.new(sx, sy, sz),
				Vector3.new(sx, sy, -sz),
				Vector3.new(sx, -sy, sz),
				Vector3.new(sx, -sy, -sz),
				Vector3.new(-sx, sy, sz),
				Vector3.new(-sx, sy, -sz),
				Vector3.new(-sx, -sy, sz),
				Vector3.new(-sx, -sy, -sz),
				}) do
				local worldCorner = relCF * corner
				minX = math.min(minX, worldCorner.X)
				minY = math.min(minY, worldCorner.Y)
				minZ = math.min(minZ, worldCorner.Z)
				maxX = math.max(maxX, worldCorner.X)
				maxY = math.max(maxY, worldCorner.Y)
				maxZ = math.max(maxZ, worldCorner.Z)
			end
		end

		local volume = (maxX - minX) * (maxY - minY) * (maxZ - minZ)
		if volume < bestVolume then
			bestVolume = volume
			bestCF = testCF * CFrame.new(
				(minX + maxX) / 2,
				(minY + maxY) / 2,
				(minZ + maxZ) / 2
			)
		end
	end

	local minX, minY, minZ = math.huge, math.huge, math.huge
	local maxX, maxY, maxZ = -math.huge, -math.huge, -math.huge
	for i = 1, #data do
		local relCF = bestCF:Inverse() * data[i].CFrame
		local size = data[i].Size
		local sx, sy, sz = size.X / 2, size.Y / 2, size.Z / 2
		for _, corner in ipairs({
			Vector3.new(sx, sy, sz),
			Vector3.new(sx, sy, -sz),
			Vector3.new(sx, -sy, sz),
			Vector3.new(sx, -sy, -sz),
			Vector3.new(-sx, sy, sz),
			Vector3.new(-sx, sy, -sz),
			Vector3.new(-sx, -sy, sz),
			Vector3.new(-sx, -sy, -sz),
			}) do
			local worldCorner = relCF * corner
			minX = math.min(minX, worldCorner.X)
			minY = math.min(minY, worldCorner.Y)
			minZ = math.min(minZ, worldCorner.Z)
			maxX = math.max(maxX, worldCorner.X)
			maxY = math.max(maxY, worldCorner.Y)
			maxZ = math.max(maxZ, worldCorner.Z)
		end
	end

	task.synchronize()

	local size = Vector3.new(maxX - minX, maxY - minY, maxZ - minZ)
	return bestCF, size
end

local function getCenterPart(cluster, cf)
	local center, bestPart, bestDist = cf.Position, nil, math.huge
	for _, part in ipairs(cluster.parts) do
		local dist = (part.Position - center).Magnitude
		if dist < bestDist then
			bestDist = dist
			bestPart = part
		end
	end
	return bestPart
end

function Physic.Process(Parts : {BasePart},IgnoreParts : {BasePart},Voxels : {BasePart},IsPreWelding : boolean)
	Parts = Parts or {}
	IgnoreParts = IgnoreParts or {}
	Voxels = Voxels or {}
	IsPreWelding = IsPreWelding or false
	WeldQueue = {}
	DeleteTable = {}
	Visited = {}
	
	local queue = Queuer:Fetch(Parts) or Queuer.New(Parts,false)
	
	if #Voxels > 0 then
		for _,voxel in pairs(Voxels) do
			local parts = workspace:GetPartBoundsInBox(voxel.CFrame,voxel.Size + vector.create(0.01,0.01,0.01))
			
			for _,part in pairs(parts) do
				if not hasRequiredTag(part) then continue end
				table.insert(Parts,part)
			end	
		end
	end
	
	local processTable = {}
	local s = os.clock()

	for _,part in pairs(Parts) do
		if not Visited[part] then
			local connected,isTouchingGround = GetConnected(part,IgnoreParts,IsPreWelding)

			if not isTouchingGround then
				FallID += 1
			end

			for i,v in pairs(connected) do
				if not v:HasTag("Anchored") then
					table.insert(processTable,v)

					if not isTouchingGround then
						--v.Parent = workspace.Fall
						local id = v:GetAttribute("_IDVoxel")

						if id then
							table.insert(DeleteTable,id)
						end

						if not v:GetAttribute("_FALLID") then
							v:SetAttribute("_FALLID",FallID)
						end
					else
						if v:HasTag("NoPhysics") then
							local id = v:GetAttribute("_IDVoxel")

							if id then
								table.insert(DeleteTable,id)
							end
						end
					end
				end
			end
		end
	end

	for i,data in pairs(WeldQueue) do
		local Weld = Cache:getWeld()
		Weld.Part0 = data[1]
		Weld.Part1 = data[2]
		Weld.Parent = data[1]
	end

	--print("Total :",MsConvert.Convert(s))

	--print(#processTable)

	for _,part in pairs(processTable) do
		part.Anchored = false
	end

	Blink.DeleteEvent.FireAll(DeleteTable)

		--[[
		local list = getShape()
		
		for _,cluster in pairs(list) do
			local isStable,failedLayers = CheckIntegrity(cluster.parts)
		end
		]]

end

--[[

local hitboxes = {}

RunService.Heartbeat:Connect(function()
	local list = getShape() 

	for _, cluster in pairs(list) do
		local vel = cluster.parts[1].AssemblyLinearVelocity
		local id = cluster.id

		if vel.Magnitude > 2 then
			if not cluster.touchGround then
				local cf, size = getBoundingBox(cluster.parts)

				local hitbox = hitboxes[id]
				if not hitbox or not hitbox.Parent then
					hitbox = Instance.new("Part")
					hitbox.Anchored = true
					hitbox.CastShadow = false
					hitbox.Transparency = 0.5
					hitbox.CanCollide = false
					hitbox.Size = size
					hitbox.CFrame = cf
					hitbox.Parent = workspace
					
					local centerPart = getCenterPart(cluster,cf)
					
					local weld = Instance.new("WeldConstraint")
					weld.Part0 = hitbox
					weld.Part1 = centerPart
					weld.Parent = hitbox
					
					hitboxes[id] = hitbox
				end
				
				game.ReplicatedStorage.Assets.Remotes.Bindable.Impact:Fire(hitbox, cluster.parts)
			end
		else
			if hitboxes[id] then
				hitboxes[id]:Destroy()
				hitboxes[id] = nil
			end
		end
	end
end)


]]
return Physic
2 Likes

Does this work it probably won’t but idk

local Physic = {}
local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")
local Workspace = game:GetService("Workspace")

local Blink = require(game.ReplicatedStorage.Blink.Server)
local Config = require(script.Parent.Config)
local MsConvert = require(script.Parent.MSConvert)
local WeldCache = require(script.WeldCache)
local Queuer = require(script.Parent.Queue)
local SAT = require(script.Parent.SAT)
local Cache = WeldCache.new(10000, 100)

local Visited = {}
local WeldQueue = {}
local WeldLookup = {}
local IDS = {}
local DeleteTable = {}
local ID = 0
local FallID = 0

local function assignId(a: BasePart)
	local id = IDS[a]

	if not id then
		ID += 1
		id = ID
		IDS[a] = id
	end

	return id
end

local function getKey(a: BasePart, b: BasePart): string
	-- Prioritize "Anchored" part as 'a' for consistent keying if one is anchored
	if a:HasTag("Anchored") and not b:HasTag("Anchored") then
		a, b = b, a
	end
	
	local aKey, bKey = assignId(a), assignId(b)

	if aKey < bKey then
		return aKey.."_"..bKey
	else
		return bKey.."_"..aKey
	end
end

local function hasRequiredTag(part)
	return part:HasTag("Anchored") or part:HasTag(Config.tag)
end

-- Function to check if two parts can be considered part of the same physics group
local function CanConnect(a : BasePart, b : BasePart)	
	-- Only connect parts with the same Anchored/Destroyable status (via tag check)
	-- If one is anchored and the other is destroyable, they shouldn't connect here
	if hasRequiredTag(a) ~= hasRequiredTag(b) then
		return false
	end
	
	-- Parts with different NoPhysics status shouldn't connect
	if a:HasTag("NoPhysics") ~= b:HasTag("NoPhysics") then
		return false
	end

	local aFall = a:GetAttribute("_FALLID")
	local bFall = b:GetAttribute("_FALLID")

	-- If one or both are falling, they must share the same FallID to connect
	-- This helps keep falling clusters isolated
	if aFall or bFall then
		if aFall ~= bFall then
			return false
		end
	end

	return true
end

local function tblToLookup(tbl)
	local lookup = {}
	for _, value in ipairs(tbl) do
		lookup[value] = true
	end
	return lookup
end

local function GetConnected(Part : BasePart,IgnoreParts : {BasePart},IsPreWelding : boolean)
	local Start = os.clock()
	
	local Optimize = tblToLookup(IgnoreParts)
	
	local Queue = {Part}
	local Results = {}
	local IsTouchingGround = false
	
	while #Queue > 0 do
		local currentPart = table.remove(Queue, 1)
		
		if not Visited[currentPart] and not Optimize[currentPart] then
			Visited[currentPart] = true
			table.insert(Results, currentPart)
			
			-- Check for direct grounding or no-physics status
			if currentPart:HasTag("Anchored") or currentPart:HasTag("NoPhysics") then
				IsTouchingGround = true
			end
			
			-- Use a slightly expanded box to find neighbors
			local neighbors = Workspace:GetPartBoundsInBox(currentPart.CFrame, currentPart.Size + Vector3.new(0.001,0.001,0.001))
			
			for _,neighbor in pairs(neighbors) do
				if neighbor:IsA("BasePart") and neighbor ~= currentPart and not Visited[neighbor] and not Optimize[neighbor]  then
					if CanConnect(currentPart, neighbor) then
						local Key = getKey(currentPart, neighbor)
						
						if neighbor:IsA("Part") then
							neighbor.Shape = Enum.PartType.Block
						end
						
						if neighbor:HasTag("Anchored") or neighbor:HasTag("NoPhysics") then
							IsTouchingGround = true
						end

						if IsPreWelding then
							table.insert(Queue, neighbor)
						else
							-- If not pre-welding, only continue searching through non-anchored parts
							if not neighbor:HasTag("Anchored") then
								table.insert(Queue, neighbor)
							end
						end
						
						if not WeldLookup[Key] then
							WeldLookup[Key] = true
							table.insert(WeldQueue, {currentPart, neighbor})
						end
					end
				end
			end
		end
	end
	
	--print("[DEBUG] Search Time :",MsConvert.Convert(Start))
	return Results, IsTouchingGround
end

local function getLayers(parts, layerThreshold)
	layerThreshold = layerThreshold or 2
	local layers = {}
	local currentLayer = {}
	local lastY = nil

	-- Sort parts by Y position to layer them (must be done outside of this function if possible for performance)
	table.sort(parts, function(a, b)
		return a.Position.Y < b.Position.Y
	end)

	for _, part in ipairs(parts) do
		if part:HasTag("Anchored") then continue end
		
		if not lastY or part.Position.Y - lastY > layerThreshold then
			if #currentLayer > 0 then
				table.insert(layers, currentLayer)
			end
			currentLayer = {}
			lastY = part.Position.Y
		end
		table.insert(currentLayer, part)
		lastY = math.max(lastY, part.Position.Y) -- Update lastY to the highest part in the current layer
	end

	if #currentLayer > 0 then
		table.insert(layers, currentLayer)
	end

	return layers
end

-- This CheckIntegrity function is generally used for complex structural failure, 
-- but often not the cause of simple 'floating'. Retained as-is for now.
local function CheckIntegrity(parts : {BasePart}, layerThreshold : number)
	layerThreshold = layerThreshold or 2

	local layers = getLayers(parts, layerThreshold)
	if not layers[1] or #layers[1] == 0 then
		warn("No bottom layer found")
		return false
	end
	
	local failedLayers = {}
	local totalMass = 0

	for i = 1, #layers - 1 do
		local lower = layers[i]
		local upper = layers[i + 1]

		local lowerMass, upperMass = 0, 0
		for _, p in ipairs(lower) do
			if p:IsA("BasePart") then
				-- Assuming totalMass accumulates mass from all layers below
				lowerMass += p:GetMass()
			end
		end
		
		-- totalMass should probably only include mass of parts *below* the current check level
		-- This logic is often complex; for simplicity, we'll use the combined mass for now.
		local supportingMass = totalMass + lowerMass
		
		for _, p in ipairs(upper) do
			if p:IsA("BasePart") then
				upperMass += p:GetMass()
			end
		end

		if supportingMass < upperMass then
			print(("Layer %d failed! Lower/Supporting mass: %.2f < Upper mass: %.2f"):format(i, supportingMass, upperMass))
			table.insert(failedLayers, lower)
		end
		
		totalMass = supportingMass -- Update total mass for next layer check
	end

	return #failedLayers == 0, failedLayers
end

local function extractPartData(parts)
	local data = table.create(#parts)
	for i, part in ipairs(parts) do
		data[i] = {
			CFrame = part.CFrame,
			Size = part.Size,
		}
	end
	return data
end

-- getBoundingBox, getCenterPart, and getShape functions were left as-is, 
-- as they relate to cluster calculation, not the basic physics problem.
function getBoundingBox(parts)
	if #parts == 0 then return nil end
	
	if #parts == 1 then
		local p = parts[1]
		return p.CFrame, p.Size
	end

	local data = extractPartData(parts)

	task.desynchronize()

	local centerPos = Vector3.zero
	for i = 1, #data do
		centerPos += data[i].CFrame.Position
	end
	centerPos /= #data

	local bestCF = CFrame.new(centerPos)
	local bestVolume = math.huge

	for i = 1, #data do
		local partCF = data[i].CFrame
		local testCF = CFrame.new(centerPos) * (partCF - partCF.Position)

		local minX, minY, minZ = math.huge, math.huge, math.huge
		local maxX, maxY, maxZ = -math.huge, -math.huge, -math.huge

		for j = 1, #data do
			local p = data[j]
			local relCF = testCF:Inverse() * p.CFrame
			local size = p.Size

			local sx, sy, sz = size.X / 2, size.Y / 2, size.Z / 2
			for _, corner in ipairs({
				Vector3.new(sx, sy, sz),
				Vector3.new(sx, sy, -sz),
				Vector3.new(sx, -sy, sz),
				Vector3.new(sx, -sy, -sz),
				Vector3.new(-sx, sy, sz),
				Vector3.new(-sx, sy, -sz),
				Vector3.new(-sx, -sy, sz),
				Vector3.new(-sx, -sy, -sz),
				}) do
				local worldCorner = relCF * corner
				minX = math.min(minX, worldCorner.X)
				minY = math.min(minY, worldCorner.Y)
				minZ = math.min(minZ, worldCorner.Z)
				maxX = math.max(maxX, worldCorner.X)
				maxY = math.max(maxY, worldCorner.Y)
				maxZ = math.max(maxZ, worldCorner.Z)
			end
		end

		local volume = (maxX - minX) * (maxY - minY) * (maxZ - minZ)
		if volume < bestVolume then
			bestVolume = volume
			bestCF = testCF * CFrame.new(
				(minX + maxX) / 2,
				(minY + maxY) / 2,
				(minZ + maxZ) / 2
			)
		end
	end

	local minX, minY, minZ = math.huge, math.huge, math.huge
	local maxX, maxY, maxZ = -math.huge, -math.huge, -math.huge
	for i = 1, #data do
		local relCF = bestCF:Inverse() * data[i].CFrame
		local size = data[i].Size
		local sx, sy, sz = size.X / 2, size.Y / 2, size.Z / 2
		for _, corner in ipairs({
			Vector3.new(sx, sy, sz),
			Vector3.new(sx, sy, -sz),
			Vector3.new(sx, -sy, sz),
			Vector3.new(sx, -sy, -sz),
			Vector3.new(-sx, sy, sz),
			Vector3.new(-sx, sy, -sz),
			Vector3.new(-sx, -sy, sz),
			Vector3.new(-sx, -sy, -sz),
			}) do
			local worldCorner = relCF * corner
			minX = math.min(minX, worldCorner.X)
			minY = math.min(minY, worldCorner.Y)
			minZ = math.min(minZ, worldCorner.Z)
			maxX = math.max(maxX, worldCorner.X)
			maxY = math.max(maxY, worldCorner.Y)
			maxZ = math.max(maxZ, worldCorner.Z)
		end
	end

	task.synchronize()

	local size = Vector3.new(maxX - minX, maxY - minY, maxZ - minZ)
	return bestCF, size
end

local function getCenterPart(cluster, cf)
	local center, bestPart, bestDist = cf.Position, nil, math.huge
	for _, part in ipairs(cluster.parts) do
		local dist = (part.Position - center).Magnitude
		if dist < bestDist then
			bestDist = dist
			bestPart = part
		end
	end
	return bestPart
end

local function getShape(visualize)
	local destroyableParts = CollectionService:GetTagged("Destroyable")
	local visited = {}
	local clusters = {}

	local function findCluster(startPart)
		local cluster = {}
		local stack = {startPart}

		while #stack > 0 do
			local current = table.remove(stack)
			if not visited[current] then
				visited[current] = true
				table.insert(cluster, current)
				
				local touching = Workspace:GetPartBoundsInBox(current.CFrame, current.Size + Vector3.new(0.001,0.001,0.001))

				for _, otherPart in ipairs(touching) do
					if otherPart:IsA("BasePart") and not visited[otherPart] and otherPart ~= current then
						-- Only consider connecting if it's destroyable (for this function's purpose)
						if otherPart:HasTag("Destroyable") then 
							table.insert(stack, otherPart)
						end
					end
				end
			end
		end
		return cluster
	end

	local function checkTouchGround(cluster)
		for _, clusterPart in ipairs(cluster) do
			if clusterPart:GetAttribute("_FALLID") then
				return false -- Already marked for falling
			end
		end
		
		-- Use OverlapParams to find if any part in the cluster touches 'Anchored' parts
		local anchoredParts = CollectionService:GetTagged("Anchored")
		local params = OverlapParams.new()
		params.FilterDescendantsInstances = anchoredParts
		params.FilterType = Enum.RaycastFilterType.Include
		
		-- Get AABB of the cluster
		local center, size = getBoundingBox(cluster)
		if not center then return false end
		
		-- Check a slightly larger area around the cluster's AABB
		local parts = Workspace:GetPartBoundsInBox(center, size + Vector3.new(0.1, 0.1, 0.1), params)
		
		return #parts > 0
	end

	for _, part in ipairs(destroyableParts) do
		if not visited[part] and not part:HasTag("Anchored") then -- Must be a destroyable, non-anchored part
			local cluster = findCluster(part)
			if #cluster > 0 then
				local firstPart = cluster[1]
				local id = firstPart:GetAttribute("_CLUSTERID")

				if not id then
					id = tick() + math.random()
					for _, p in ipairs(cluster) do
						p:SetAttribute("_CLUSTERID", id)
					end
				end

				local clusterData = {
					parts = cluster,
					touchGround = checkTouchGround(cluster),
					id = id,
				}
				table.insert(clusters, clusterData)
			end
		end
	end

	-- Visualization code (optional but helpful)
	if visualize then
		for i, clusterData in ipairs(clusters) do
			local model = Instance.new("Model")
			model.Name = "Cluster_" .. i .. (clusterData.touchGround and "_Grounded" or "_Floating")
			model.Parent = Workspace

			local highlight = Instance.new("Highlight")
			highlight.FillColor = clusterData.touchGround and Color3.new(0, 1, 0) or Color3.new(1, 0, 0)
			highlight.OutlineColor = Color3.new(1, 1, 1)
			highlight.Parent = model

			for _, part in ipairs(clusterData.parts) do
				part.Parent = model
			end
		end
	end

	return clusters
end

function Physic.Process(Parts : {BasePart},IgnoreParts : {BasePart},Voxels : {BasePart},IsPreWelding : boolean)
	Parts = Parts or {}
	IgnoreParts = IgnoreParts or {}
	Voxels = Voxels or {}
	IsPreWelding = IsPreWelding or false
	WeldQueue = {}
	WeldLookup = {} -- Clear the lookup for each process
	DeleteTable = {}
	Visited = {}
	
	-- Only clear IDS if you want to reuse IDs, otherwise ID generation continues
	-- IDS = {} 
	
	local queue = Queuer:Fetch(Parts) or Queuer.New(Parts,false)
	
	-- Voxel logic for finding nearby parts
	if #Voxels > 0 then
		for _,voxel in pairs(Voxels) do
			local parts = Workspace:GetPartBoundsInBox(voxel.CFrame,voxel.Size + Vector3.new(0.01,0.01,0.01))
			
			for _,part in pairs(parts) do
				if part:IsA("BasePart") and hasRequiredTag(part) and not Visited[part] then -- Added type check and visited check
					table.insert(Parts,part)
				end	
			end
		end
	end
	
	local processTable = {} -- Parts that should be unanchored
	local groundedWeldQueue = {} -- Welds only for grounded clusters
	local fallingWeldQueue = {} -- Welds only for falling clusters
	
	local s = os.clock()

	for _,part in pairs(Parts) do
		if part:IsA("BasePart") and not Visited[part] then
			local connected,isTouchingGround = GetConnected(part,IgnoreParts,IsPreWelding)
			
			local clusterWeldQueue = {}
			
			-- Filter welds for this specific cluster
			local newWeldQueue = {}
			for i = 1, #WeldQueue do
				local a, b = WeldQueue[i][1], WeldQueue[i][2]
				local inCluster = false
				for _, p in ipairs(connected) do
					if p == a or p == b then
						inCluster = true
						break
					end
				end
				if inCluster then
					table.insert(clusterWeldQueue, WeldQueue[i])
					-- Prevent them from being added to the main WeldQueue again if we iterate on a subsequent part
				else
					table.insert(newWeldQueue, WeldQueue[i])
				end
			end
			WeldQueue = newWeldQueue
			
			if not isTouchingGround then
				FallID += 1
				
				-- If NOT touching ground, assign a FALLID and prep for unanchoring
				for _,v in pairs(connected) do
					if not v:HasTag("Anchored") and not v:HasTag("NoPhysics") then -- Never unanchor anchored or NoPhysics parts
						table.insert(processTable, v)

						local id = v:GetAttribute("_IDVoxel")
						if id then
							table.insert(DeleteTable, id)
						end

						v:SetAttribute("_FALLID", FallID) -- Mark all parts in this falling cluster
					end
				end
				
				-- Add welds to the falling queue
				for _, weldData in ipairs(clusterWeldQueue) do
					table.insert(fallingWeldQueue, weldData)
				end
				
			else
				-- If TOUCHING ground, weld them together but do NOT unanchor
				for _, weldData in ipairs(clusterWeldQueue) do
					table.insert(groundedWeldQueue, weldData)
				end
				
				-- If it's a "NoPhysics" part, but is now touching ground, ensure it's removed from deletion queue
				for _, v in ipairs(connected) do
					if v:HasTag("NoPhysics") then
						local id = v:GetAttribute("_IDVoxel")
						if id then
							table.insert(DeleteTable, id)
						end
					end
				end
			end
		end
	end
	
	-- ----------------------------------------------------
	-- APPLY WELDS
	-- ----------------------------------------------------
	
	-- Weld the grounded clusters (they will remain anchored if they started that way)
	for _,data in pairs(groundedWeldQueue) do
		local Weld = Cache:getWeld()
		Weld.Part0 = data[1]
		Weld.Part1 = data[2]
		Weld.Parent = data[1]
	end

	-- Weld the falling clusters (to make them one solid assembly)
	for _,data in pairs(fallingWeldQueue) do
		local Weld = Cache:getWeld()
		Weld.Part0 = data[1]
		Weld.Part1 = data[2]
		Weld.Parent = data[1]
	end
	
	--print("Total :",MsConvert.Convert(s))

	-- ----------------------------------------------------
	-- UNANCHOR AND FIRE EVENTS
	-- ----------------------------------------------------
	
	-- Finally, unanchor the parts identified as non-grounded
	for _,part in pairs(processTable) do
		part.Anchored = false
		-- Important: Unset the FallID after it has been fully processed and unanchored
		part:SetAttribute("_FALLID", nil) 
	end

	Blink.DeleteEvent.FireAll(DeleteTable)

	-- Note: The commented out CheckIntegrity and RunService loop 
	-- were left commented out as in your original code.

		--[[
		local list = getShape()
		
		for _,cluster in pairs(list) do
			local isStable,failedLayers = CheckIntegrity(cluster.parts)
		end
		]]

end

--[[

local hitboxes = {}

RunService.Heartbeat:Connect(function()
	local list = getShape() 

	for _, cluster in pairs(list) do
		local vel = cluster.parts[1].AssemblyLinearVelocity
		local id = cluster.id

		if vel.Magnitude > 2 then
			if not cluster.touchGround then
				local cf, size = getBoundingBox(cluster.parts)

				local hitbox = hitboxes[id]
				if not hitbox or not hitbox.Parent then
					hitbox = Instance.new("Part")
					hitbox.Anchored = true
					hitbox.CastShadow = false
					hitbox.Transparency = 0.5
					hitbox.CanCollide = false
					hitbox.Size = size
					hitbox.CFrame = cf
					hitbox.Parent = Workspace
					
					local centerPart = getCenterPart(cluster,cf)
					
					local weld = Instance.new("WeldConstraint")
					weld.Part0 = hitbox
					weld.Part1 = centerPart
					weld.Parent = hitbox
					
					hitboxes[id] = hitbox
				end
				
				game.ReplicatedStorage.Assets.Remotes.Bindable.Impact:Fire(hitbox, cluster.parts)
			end
		else
			if hitboxes[id] then
				hitboxes[id]:Destroy()
				hitboxes[id] = nil
			end
		end
	end
end)


]]
return Physic
1 Like

holy chatgpt it didn’t work tho but I notice that it start floating when it like run physics multiple times idk tho

Do you require the module multiple times with different scripts?

did you tag the parts? also why do you need custom physics?

yeah i am thinking the same as you. why do you need custom physics?

it used for my voxel destruction game since it create a new part I need to weld them and make it fall

The logic of your script makes the whole thing floating because it marks all the cluster as grounded, this blocks the parts from falling, by marking Parts as Anchored or NoPhysics. Grounded Clusters won’t fall, even if they aren’t anchored since they can’t get _FALLID.

Here is what you can try:

  • make sure every true support part has Anchored tag, avoiding NoPhysics for ground checks.
  • Try adding debug messages to your script to understand where your problem is.
  • Check no part in the cluster is welded to an anchored part or anchored itself

so everything is tag correctly but I noticed that some part has weld that is too far away to actually weld I don’t know the reason why it does that

1 Like

A tip to prevent this from happening is a distance check before putting parts into Weldqueue.
Example:

if (currentPart.Position - neighbor.Position).Magnitude < threshold then
    table.insert(WeldQueue, {currentPart, neighbor})
end

I did try this and it still weld too far away I have no idea why it does that and I do this instead distance checking

if not WeldLookup[Key] and SAT.Colliding(currentPart,neighbor) then
	WeldLookup[Key] = true
	table.insert(WeldQueue,{currentPart,neighbor})
end
1 Like

why not just weld the parts and unanchor them once they are welded?

I think it gonna cause smth to happen bc like if you didn’t wait for weld to be done it might cause the part to move before the weld but idk (I just try it and it broke stuff so yeah)

have you tried anchoring the parts before welding them?

yea I try it on my voxel module

local function copyPart(source: BasePart, cframe: CFrame, size: Vector3, isVoxel: boolean?): BasePart
	isVoxel = isVoxel or false
	
	local part = cache:GetPart()
	part.CFrame = cframe
	part.Size = size
	part.Anchored = true
	part.CanCollide = source.CanCollide
	part.Color = source.Color
	part.Transparency = source.Transparency
	part.Reflectance = source.Reflectance
    part.TopSurface = source.TopSurface
    part.Material = source.Material
    part.AssemblyLinearVelocity = source.AssemblyLinearVelocity
	part.AssemblyAngularVelocity = source.AssemblyAngularVelocity
	part.Massless = source.Massless
    
	if not isVoxel then
		id += 1 + tick()

		part:SetAttribute("_IDVoxel", id)
		
		local data = serializePart(source, cframe, size, source:GetAttribute("_OriginalTransparency"), source:GetAttribute("_OriginalCastShadow"),source:HasTag("Light"),id)

		for _, tag in ipairs(source:GetTags()) do
			part:AddTag(tag)
		end

		for attribute, value in pairs(source:GetAttributes()) do
			if attribute ~= "_IDVoxel" then
				part:SetAttribute(attribute, value)
			end
        end
        
		table.insert(pending.parts,data)
	else
        part:AddTag("Voxel")
	end

	part.Parent = GlobalServer
	
	return part
end

I am a bit confused why doesn’t something like this work then?

I do like this also I think set parent first is not the best and I want the part that weld to be touched if I try to unanchored both part after done it just break everything

	for _,data in pairs(WeldQueue) do
		local Weld = Cache:getWeld()
		Weld.Part0 = data[1]
		Weld.Part1 = data[2]
		Weld.Parent = data[1]
	end

	Blink.DeleteEvent.FireAll(DeleteTable)

	for _,part in pairs(processTable) do
		part.Anchored = false
	end

and before this all processed even happen it take this

        if #physicTable > 0 then
			Physic.Process(physicTable,partsToRemove,voxelParts)
		end

which is come from this

        for _, block in ipairs(results) do
            local part = copyPart(partsToRemove[block.D],block.CFrame,block.Size)
            
            if partsToRemove[block.D]:HasTag("Light") then
                part:RemoveTag("Light")

                task.spawn(function()
                    local flickers = math.random(3, 5)
                    local originalColor = part.Color
                    local offColor = Color3.new(0.1, 0.1, 0.1) -- dark gray/off look

                    for i = 1, flickers do
                        part.Color = originalColor
                        task.wait()

                        part.Color = offColor
                        task.wait()
                    end

                    part.Color = offColor
                end)
            end
			
            table.insert(chunk[block.D],part)
            table.insert(physicTable,part)
		end

if you want more detail you can ask :wink:

you forgot the c0 which breaks the offset

im using a WeldConstraint since it better ig

I might be the stupidest person on this planet I has this module that preserved cons and my old version it tag to weld to be like physics weld so it would skip but it my current weld cache module IT didn’t add any tag so yea

--[[
This module is used for preserving the constraint of a part to other parts.
]]

--!native
--!optimize 2

local GeometryService = game:GetService("GeometryService")
local constraintOptions = {}
constraintOptions.tolerance=0.2
constraintOptions.weldConstraintPreserve=Enum.WeldConstraintPreserve.Touching

local module = {}

-- this is just for keep track lol!
local everyConstraint = {
    "RopeConstraint",
    "HingeConstraint",
    "SpringConstraint"
}

local everyConstraintProp = {
	RopeConstraint = {"Length", "Thickness", "Restitution", "Visible", "Color", "Enabled"},
	HingeConstraint = {"ActuatorType", "LimitsEnabled", "AngularVelocity", "MotorMaxTorque", "MotorMaxAcceleration", "LowerAngle", "UpperAngle", "Enabled"},
}

function module._FindClosestPart(targetPos: Vector3, parts: {BasePart})
    local closestPart = nil
    local shortestDistance = math.huge

	for _, part in ipairs(parts) do
		if not part.Name then continue end
		
        local distance = (part.Position - targetPos).Magnitude
        if distance < shortestDistance then
            shortestDistance = distance
            closestPart = part
        end
    end

    return closestPart, shortestDistance
end

function module._CheckForConstraint(part: BasePart)
    local joints = part:GetJoints()
    local hasConstraint = #joints > 0
    return hasConstraint, joints
end

function module.PreserveConstraint(part: BasePart, chunk : {BasePart})
	local hasConstraint, constraints = module._CheckForConstraint(part)
	if not hasConstraint then return end

	for _, constraint in ipairs(constraints) do
		if constraint:HasTag("PhysicsWeld") then continue end

		if constraint.ClassName == "RopeConstraint" then
			local att0 = constraint.Attachment0 :: Attachment
			local att1 = constraint.Attachment1 :: Attachment
			if not att0 or not att1 then continue end

			local createAtt0 = att0:IsDescendantOf(part)
			local createAtt1 = att1:IsDescendantOf(part)

			local preservedPart1 = module._FindClosestPart(att0.WorldPosition, chunk) -- so we find the preserved part1 if the create att0 is false!!
			local preservedPart2 = module._FindClosestPart(att1.WorldPosition, chunk) -- the same here
			if not preservedPart1 or not preservedPart2 then continue end

			local newConstraint = Instance.new("RopeConstraint")

			local newAtt0, newAtt1

			if createAtt0 then
				newAtt0 = Instance.new("Attachment")
				newAtt0.CFrame = preservedPart1.CFrame:ToObjectSpace(att0.WorldCFrame)
				newAtt0.Parent = preservedPart1
			else
				newAtt0 = att0
			end

			if createAtt1 then
				newAtt1 = Instance.new("Attachment")
				newAtt1.CFrame = preservedPart2.CFrame:ToObjectSpace(att1.WorldCFrame)
				newAtt1.Parent = preservedPart2
			else
				newAtt1 = att1
			end

			newConstraint.Attachment0 = newAtt0
			newConstraint.Attachment1 = newAtt1

			for _, prop in ipairs(everyConstraintProp["RopeConstraint"]) do
				newConstraint[prop] = constraint[prop]
			end

			newConstraint.Parent = preservedPart1

			if newConstraint.Attachment0 and newConstraint.Attachment1 then
				constraint:Destroy()
			end
		end

		--make it work on the spiny part currently destroy the base still make the spiny part still rotate but when destroy spiny part it now stop :(
		if constraint.ClassName == "HingeConstraint" then
			local att0 = constraint.Attachment0 :: Attachment
			local att1 = constraint.Attachment1 :: Attachment
			if not att0 or not att1 then continue end

			local createAtt0 = att0:IsDescendantOf(part)
			local createAtt1 = att1:IsDescendantOf(part)

			local preservedPart1 = module._FindClosestPart(att0.WorldPosition, chunk) -- so we find the preserved part1 if the create att0 is false!!
			local preservedPart2 = module._FindClosestPart(att1.WorldPosition, chunk) -- the same here
			if not preservedPart1 or not preservedPart2 then continue end

			local newConstraint = Instance.new("HingeConstraint")

			local newAtt0, newAtt1

			if createAtt0 then
				newAtt0 = Instance.new("Attachment")
				newAtt0.CFrame = preservedPart1.CFrame:ToObjectSpace(att0.WorldCFrame)
				newAtt0.Parent = preservedPart1
			else
				newAtt0 = att0
			end

			if createAtt1 then
				newAtt1 = Instance.new("Attachment")
				newAtt1.CFrame = preservedPart2.CFrame:ToObjectSpace(att1.WorldCFrame)
				newAtt1.Parent = preservedPart2
			else
				newAtt1 = att1
			end

			newConstraint.Attachment0 = newAtt0
			newConstraint.Attachment1 = newAtt1

			for _, prop in ipairs(everyConstraintProp["HingeConstraint"]) do
				newConstraint[prop] = constraint[prop]
			end

			newConstraint.Parent = preservedPart1

			if newConstraint.Attachment0 and newConstraint.Attachment1 then
				constraint:Destroy()
			end
		end

		if constraint.ClassName == "WeldConstraint" then

			local part0 = constraint.Part0
			local part1 = constraint.Part1

			local preservedPart0 = module._FindClosestPart(part0.Position, chunk)
			local preservedPart1 = module._FindClosestPart(part1.Position, chunk)

			if preservedPart1 then
				local weld = Instance.new("WeldConstraint")
				weld.Part0 = part0
				weld.Part1 = preservedPart1
				weld.Parent = part0
			end

			if preservedPart0 then
				local weld = Instance.new("WeldConstraint")
				weld.Part0 = part1
				weld.Part1 = preservedPart0
				weld.Parent = part1
			end
		end
	end
end

return module

old

--!native
--!optimize 2

local WeldCache = {}
WeldCache.__index = WeldCache


local Weld = Instance.new("Folder")
Weld.Name = "WeldsFolder"
Weld.Parent = workspace

function WeldCache.new(cacheSize, expandSize)
	local self = setmetatable({}, WeldCache)
	self.cacheSize = cacheSize or 100
	self.expandSize = expandSize or 50
	self.available = {}
	self.totalWelds = 0

	for i = 1, self.cacheSize do
		local weld = Instance.new("WeldConstraint")
		weld.Parent = Weld
		weld:AddTag("PhysicsWeld")
		table.insert(self.available, weld)
		self.totalWelds = self.totalWelds + 1
	end

	return self
end

function WeldCache:getWeld()
	if #self.available == 0 then
		for i = 1, self.expandSize do
			local weld = Instance.new("WeldConstraint")
			table.insert(self.available, weld)
			self.totalWelds = self.totalWelds + 1
		end
	end

	local weld = table.remove(self.available)

	return weld
end

return WeldCache

new

--!native
--!optimize 2

local WeldCache = {}
WeldCache.__index = WeldCache


local Weld = Instance.new("Folder")
Weld.Name = "WeldsFolder"
Weld.Parent = workspace

function WeldCache.new(cacheSize, expandSize)
	local self = setmetatable({}, WeldCache)
	self.cacheSize = cacheSize or 100
	self.expandSize = expandSize or 50
	self.available = {}
	self.totalWelds = 0

	for i = 1, self.cacheSize do
		local weld = Instance.new("WeldConstraint")
		weld.Parent = Weld
		--weld:AddTag("PhysicsWeld")
		table.insert(self.available, weld)
		self.totalWelds = self.totalWelds + 1
	end

	return self
end

function WeldCache:getWeld()
	if #self.available == 0 then
		for i = 1, self.expandSize do
			local weld = Instance.new("WeldConstraint")
			table.insert(self.available, weld)
			self.totalWelds = self.totalWelds + 1
		end
	end

	local weld = table.remove(self.available)

	return weld
end

return WeldCache

and also in getWeld

function WeldCache:getWeld()
	if #self.available == 0 then
		for i = 1, self.expandSize do
			local weld = Instance.new("WeldConstraint")
			table.insert(self.available, weld)
			self.totalWelds = self.totalWelds + 1
		end
	end

	local weld = table.remove(self.available)

	return weld
end

if weld run out it create a new weld which is correct BUT it didn’t tag physics weld so yeah im kinda bum oh omg

1 Like