Hi guys ive been doing voxel destruction stuff for a long time now but I still can’t figure this out so currently my module took search time for preweld is 436ms ig it a lot it don’t even big map but after preweld it getting 40ms-12ms but I still want a way to optimize it and my search split time is 850ms-750ms on the urban house I already create topic about it but I only get suggest to rewrite which I does but I still can’t figure thing out
the physics code (it so messy don’t worry about it)
local physic = {}
physic.groups = {} -- :(
physic.fallTable = {}
physic.groupLookup = {}
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 fea = require(script.FEA)
local cache = weldCache.new(10000, 500)
local ingerityRemote = game.ReplicatedStorage.Assets.Remote.Bindable.Server.ingerity
local impactRemote = game.ReplicatedStorage.Assets.Remote.Bindable.Server.impact
--[[
1. Weld Every parts efficiently!!!
]]
local isDebug = false
local debugLog = {}
local visited = {}
local weldQueue = {}
local weldLookup = {}
local ids = {}
local deleteTable = {}
local currentId = 0
local currentGroupId = 0
local groupFolders = {}
local impactLookup = {}
local impactQueue = {}
local function assignId(a: BasePart)
local id = ids[a]
if not id then
currentId += 1
id = currentId
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
if physic.fallTable[a] ~= physic.fallTable[b] then
return false
end
if not a:HasTag("Anchored") and not b:HasTag("Anchored") then
if physic.groups[a] ~= physic.groups[b] then
return false
end
end
return true
end
local function getConnected(part: BasePart, ignoreParts: {BasePart}, isPreWelding: boolean)
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
overlapParams.FilterDescendantsInstances = ignoreParts
local queue = {part}
local results = {}
local isTouchingGround = false
local start = os.clock()
while #queue > 0 do
local current = table.remove(queue, 1)
if not visited[current] then
visited[current] = true
table.insert(results, current)
local neighbors = workspace:GetPartBoundsInBox(
current.CFrame,
current.Size + Vector3.new(0.001, 0.001, 0.001), -- Changed vector.create to Vector3.new for standard Luau, or keep vector.create if using native vector library
overlapParams
)
if current:HasTag("Anchored") or current:HasTag("NoPhysics") then
isTouchingGround = true
end
for _, neighbor in ipairs(neighbors) do
local key = getKey(current, neighbor)
if neighbor ~= current and not visited[neighbor] then
if isTouchingGround and weldLookup[key] and not isPreWelding then continue end
if canConnect(current, neighbor) then
if neighbor:HasTag("Anchored") then
isTouchingGround = true
else
table.insert(queue, neighbor)
end
if not weldLookup[key] then
weldLookup[key] = true
table.insert(weldQueue, {current, neighbor})
end
end
end
end
end
end
print("search time? :",msConvert.Convert(start))
return results, isTouchingGround
end
local function colorLayersGradient(layers)
local numLayers = #layers
for i, layer in ipairs(layers) do
local color = Color3.new(math.random(), math.random(), math.random())
for _, part in ipairs(layer) do
part.Color = color
end
end
end
local function getLayers(parts, layerHeight)
layerHeight = layerHeight or 16
local layersMap = {}
for _, part in ipairs(parts) do
if part:HasTag("Anchored") then continue end
local layerIndex = math.floor(part.Position.Y / layerHeight)
layersMap[layerIndex] = layersMap[layerIndex] or {}
table.insert(layersMap[layerIndex], part)
end
local layers = {}
for _, layerParts in pairs(layersMap) do
table.insert(layers, layerParts)
end
colorLayersGradient(layers)
return layers
end
local function arePartsTouching(partA, partB)
return sat.Colliding(partA, partB)
end
local function checkStructure(cluster)
local fallLayers = {}
local trussStrengthMultiplier = 4
for _, parts in pairs(cluster) do
local layers = getLayers(parts)
for i, layer in ipairs(layers) do
local upperLayer = layer
local belowLayer = layers[i + 1]
if belowLayer then
local currentLoad = 0
for _, part in pairs(upperLayer) do
currentLoad += 10
end
local effectiveSupport = 0
for _, supportPart in pairs(belowLayer) do
if supportPart:HasTag("NoWeight") then continue end
local isConnected = false
for _, loadPart in pairs(upperLayer) do
if arePartsTouching(loadPart, supportPart) then
isConnected = true
break
end
end
if isConnected or i == #layers - 1 then
local partStrength = 10
effectiveSupport += (partStrength * trussStrengthMultiplier)
end
end
print("Layer", i, "| Load:", currentLoad, "| Effective Support:", effectiveSupport)
if effectiveSupport < currentLoad then
table.insert(fallLayers, upperLayer)
end
end
end
end
return fallLayers
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)
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
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) do
local dist = (part.Position - center).Magnitude
if dist < bestDist then
bestDist = dist
bestPart = part
end
end
return bestPart
end
local hitboxs = {}
local function searchSpliting(startPart, group : {BasePart}, ourVisited)
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Include
overlapParams.FilterDescendantsInstances = group
local queue = {startPart}
local results = {}
local start = os.clock()
while #queue > 0 do
local current = table.remove(queue, 1)
if not ourVisited[current] then
ourVisited[current] = true
table.insert(results, current)
local neighbors = workspace:GetPartBoundsInBox(
current.CFrame,
current.Size + Vector3.new(0.001, 0.001, 0.001),
overlapParams
)
for _, neighbor in ipairs(neighbors) do
if neighbor ~= current and not ourVisited[neighbor] then
table.insert(queue, neighbor)
end
end
end
end
print("search spliting time? :", msConvert.Convert(start))
return results
end
local function getSpliting(group : {BasePart})
local ourVisited = {}
local clusters = {}
table.sort(group, function(a, b)
return a.Position.Y < b.Position.Y
end)
for _, part in ipairs(group) do
if part.Parent and not ourVisited[part] then
local spliting = searchSpliting(part, group, ourVisited)
table.insert(clusters, spliting)
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 = {}
deleteTable = {}
visited = {}
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Include
overlapParams.FilterDescendantsInstances = ignoreParts
local list = {}
local processTable = {}
local check = {}
for _, part in pairs(parts) do
if not part:IsA("BasePart") then continue end
if visited[part] then continue end
local connected, isTouchingGround = getConnected(part, ignoreParts, isPreWelding)
local groupId = physic.groups[connected[1]]
if not groupId then
currentGroupId += 1
groupId = currentGroupId
end
if not groupFolders[groupId] then
-- create group folder
groupFolders[groupId] = Instance.new("Model")
groupFolders[groupId].Name = "Group" .. groupId
groupFolders[groupId].Parent = workspace
-- create highlight
--local highlight = Instance.new("Highlight")
--highlight.Name = "GroupHighlight"
--highlight.Parent = groupFolders[groupId]
-- random color
--highlight.FillColor = Color3.new(math.random(), math.random(), math.random())
--highlight.OutlineColor = Color3.new(math.random(), math.random(), math.random())
-- optionally, adjust transparency
--highlight.FillTransparency = 0.5
--highlight.OutlineTransparency = 0.3
end
if not physic.groupLookup[groupId] then
physic.groupLookup[groupId] = {}
end
if not check[groupId] then -- if not already in the check
check[groupId] = physic.groupLookup[groupId]
end
for i, v in pairs(connected) do
if not v:HasTag("Anchored") then
v.Parent = groupFolders[groupId]
if not physic.groups[v] then
physic.groups[v] = groupId
end
table.insert(physic.groupLookup[groupId],v)
if not isTouchingGround then
local id = v:GetAttribute("_IDVoxel")
if id then
table.insert(deleteTable, id)
end
if not physic.fallTable[v] then
physic.fallTable[v] = true
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
if isDebug then
if #debugLog > 0 then
print("\n===== DEBUG SEARCH RESULTS =====")
print("Current | Neighbor | Distance")
print(debugLog)
debugLog = {}
print("================================\n")
end
end
local s = os.clock()
for i, data in ipairs(weldQueue) do
task.defer(function()
local weld = cache:getWeld()
weld.Part0 = data[1]
weld.Part1 = data[2]
weld.Parent = data[1]
data[1].Anchored = data[1]:HasTag("Anchored")
data[2].Anchored = data[2]:HasTag("Anchored")
end)
end
--print(#processTable)
print("Total :", msConvert.Convert(s))
blink.DeleteEvent.FireAll(deleteTable)
if isPreWelding then
cache:destroy()
end
task.spawn(function()
for _,parts in pairs(check) do
local clusters = getSpliting(parts)
if #clusters > 1 then
currentGroupId += 1
local groupId = currentGroupId
if not groupFolders[groupId] then
-- create group folder
groupFolders[groupId] = Instance.new("Model")
groupFolders[groupId].Name = "Group" .. groupId
groupFolders[groupId].Parent = workspace
-- create highlight
--local highlight = Instance.new("Highlight")
--highlight.Name = "GroupHighlight"
--highlight.Parent = groupFolders[groupId]
-- random color
--highlight.FillColor = Color3.new(math.random(), math.random(), math.random())
--highlight.OutlineColor = Color3.new(math.random(), math.random(), math.random())
-- optionally, adjust transparency
--highlight.FillTransparency = 0.5
--highlight.OutlineTransparency = 0.3
end
if not physic.groupLookup[groupId] then
physic.groupLookup[groupId] = {}
end
for _,part in pairs(clusters[1]) do
physic.groups[part] = groupId
table.insert(physic.groupLookup[groupId],part)
part.Parent = groupFolders[groupId]
end
end
end
end)
end
local function getCorners(part)
local cf = part.CFrame
local size = part.Size / 2
local corners = {}
for x = -1, 1, 2 do
for y = -1, 1, 2 do
for z = -1, 1, 2 do
table.insert(corners, cf * Vector3.new(size.X * x, size.Y * y, size.Z * z))
end
end
end
return corners
end
local edgeIndexes = {
{1,2},{1,3},{1,5},
{8,7},{8,6},{8,4},
{2,4},{2,6},
{3,4},{3,7},
{5,6},{5,7}
}
local function getContactPoints(partA, partB)
local points = {}
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Include
params.FilterDescendantsInstances = {partA, partB}
local function test(part1, part2)
local corners = getCorners(part1)
for _, edge in ipairs(edgeIndexes) do
local p1 = corners[edge[1]]
local p2 = corners[edge[2]]
local dir = (p2 - p1)
for _, mul in ipairs({1, -1}) do
local rayDir = dir.Unit * dir.Magnitude * mul
local result = workspace:Raycast(p1, rayDir, params)
if result and result.Instance == part2 then
table.insert(points, result.Position)
end
end
end
end
test(partA, partB)
test(partB, partA)
local center = nil
if #points > 0 then
local sum = Vector3.zero
for _, p in ipairs(points) do
sum += p
end
center = sum / #points
end
return points, center
end
local accumulator = 0
local interval = 0.1
local keThreshold = 10
local function getLargestAxis(part: Part)
return math.max(part.Size.X, part.Size.Y, part.Size.Z)
end
--[[
runService.Heartbeat:Connect(function(dt)
accumulator = accumulator + dt
if accumulator < interval then return end
accumulator = 0
for clusterId, data in pairs(impactQueue) do
local parts = data[1]
local id = data[2]
local cf = data[3]
local size = data[4]
local centerPart = data[5]
if not hitboxs[id] then
local hitbox = Instance.new("Part")
hitbox.Name = "Hitbox"
hitbox.Anchored = false
hitbox.CanCollide = false
hitbox.Transparency = 0.5
hitbox.Size = size + Vector3.new(0.001, 0.001, 0.001)
hitbox.CFrame = cf
hitbox.Parent = workspace
local weld = cache:getWeld()
weld.Part0 = hitbox
weld.Part1 = centerPart
weld.Parent = hitbox
hitboxs[id] = {parts = parts, hitbox = hitbox, centerPart = centerPart}
else
local mass = centerPart.AssemblyMass
local velocity = centerPart.AssemblyLinearVelocity.Magnitude
local ke = 0.5 * mass * velocity^2
if ke >= keThreshold then
local params = OverlapParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = {hitboxs[id].parts, hitboxs[id].hitbox}
local parts = workspace:GetPartBoundsInBox(hitboxs[id].hitbox.CFrame, hitboxs[id].hitbox.Size, params)
for _, part: BasePart in pairs(parts) do
if not hasRequiredTag(part) then continue end
local points = getContactPoints(hitboxs[id].hitbox, part)
impactRemote:Fire(points)
end
end
end
end
end)
]]
return physic