Okay so first off you can make your GetBlocked function *soooo* much more readable like this:
Having magic numbers like you do all over the place is usually a bad idea. Give the numbers a descriptive name like this:
(also changed table.insert(Blocked, table_index, value)
to Blocked[table_index]=value
. Should be slightly faster and a bunch shorter)
local DIR_ID_UP = 1
local DIR_ID_DOWN = 2
local DIR_ID_RIGHT = 3
local DIR_ID_LEFT = 4
local DIR_ID_DOWNRIGHT = 5
local DIR_ID_DOWNLEFT = 6
local DIR_ID_UPRIGHT = 7
local DIR_ID_UPLEFT = 8
function GetBlocked(v,Neighbors)
local Blocked = {} --Up,Down,Right,Left,DownRight,DownLeft,UpRight,UpLeft
for _,v2 in pairs(Neighbors) do
if v2.Name == "Part" then
if v2.Position.Z == v.Position.Z-1 and v2.Position.X == v.Position.X then
Blocked[DIR_ID_DOWN] = true
end
if v2.Position.Z == v.Position.Z+1 and v2.Position.X == v.Position.X then
Blocked[DIR_ID_UP] = true
end
if v2.Position.X == v.Position.X-1 and v2.Position.Z == v.Position.Z then
Blocked[DIR_ID_RIGHT] = true
end
if v2.Position.X == v.Position.X+1 and v2.Position.Z == v.Position.Z then
Blocked[DIR_ID_LEFT] = true
end
if v2.Position == v.Position+Vector3.new(-1,0,-1) then
Blocked[DIR_ID_DOWNRIGHT] = true
end
if v2.Position == v.Position+Vector3.new(1,0,-1) then
Blocked[DIR_ID_DOWNLEFT] = true
end
if v2.Position == v.Position+Vector3.new(-1,0,1) then
Blocked[DIR_ID_UPRIGHT] = true
end
if v2.Position == v.Position+Vector3.new(1,0,1) then
Blocked[DIR_ID_UPLEFT] = true
end
end
end
return Blocked
end
Next, you can replace your current "neighbor finding approach" with a simple lookup. That should be a lot faster than doing actual 3D collision detection with GetTouchingParts. You can use a 2D map for it:
local particles = {}
function mapGet(map, c1, c2)
if map[c1] then --Could be omitted if you initialize map to be all empty tables, e.g. particles = table.create({}, mapWidth)
return map[c1][c2]
end
end
function mapSet(map, c1, c2, value)
map[c1] = map[c1] or {} --Could be omitted if you initialize map to be all empty tables, e.g. particles = table.create({}, mapWidth)
map[c1][c2] = value
end
Your GetNeighbors function can then look like
function getNeighbors(point)
local neighbors = {}
local x0, y0 = point.X, point.Y
local n1, n2, n3, n4 =
mapGet(particles, x0 + 1, y0),
mapGet(particles, x0, y0 + 1),
mapGet(particles, x0 - 1, y0),
mapGet(particles, x0, y0 - 1)
if n1 then neighbors[#neighbors+1] = n1 end
if n2 then neighbors[#neighbors+1] = n2 end
if n2 then neighbors[#neighbors+1] = n3 end
if n2 then neighbors[#neighbors+1] = n4 end
return neighbors
end
Now that I'm starting to understand your code a bit, it seems like your `GetBlocked` function could be improved. A particle can presumably only "block" a single coordinate, so if you've determined which coordinate a particle blocks, you don't need to check if it blocks any other coordinates:
function GetBlocked(v,Neighbors)
local Blocked = {} --Up,Down,Right,Left,DownRight,DownLeft,UpRight,UpLeft
for _,v2 in pairs(Neighbors) do
if v2.Name == "Part" then
if v2.Position.Z == v.Position.Z-1 and v2.Position.X == v.Position.X then
Blocked[DIR_ID_DOWN] = true
continue --Dont' check the other coordinates
end
...
if v2.Position == v.Position+Vector3.new(1,0,1) then
Blocked[DIR_ID_UPLEFT] = true
continue
end
end
end
return Blocked
The biggest issue is probably that for each particle you’re checking every other particle to see if the destination for a particle is blocked, which gives it a running time of like O(n^2), i.e. slow as heck. Using the 2d map approach can make it O(n), i.e. decently fast.
I tried making a version based on what you posted, here it is:
local particles = game.Workspace:WaitForChild"Parts"):GetChildren() --array
local particleMap = {} --2d map
function mapGet(map, c1, c2)
if map[c1] then --Could be omitted if you initialize map to be all empty tables, e.g. particleMap = table.create({}, mapWidth)
return map[c1][c2]
end
end
function mapSet(map, c1, c2, value)
map[c1] = map[c1] or {} --Could be omitted if you initialize map to be all empty tables, e.g. particleMap = table.create({}, mapWidth)
map[c1][c2] = value
end
--Build particleMap based on where the particles start
for _, particle in pairs(particles) do
local pos = particle.Position
assert( not mapGet(particleMap, pos.X, pos.Z), ("Two particles have the same position (%s)"):format(tostring(pos)) )
assert( pos.X%1 == 0 and pos.Z%1 == 0, ("A particle has non-integer coordinates (%s)"):format(tostring(pos)))
mapSet(particleMap, pos.X, pos.Z, particle)
end
local neighbor_dirs = {
Vector3.new(1, 0, 0),
Vector3.new(1, 0, 1),
Vector3.new(0, 0, 1),
Vector3.new(-1, 0, 1),
Vector3.new(-1, 0, 0),
Vector3.new(-1, 0, -1),
Vector3.new(0, 0, -1),
Vector3.new(1, 0, -1),
}
local DIR_ID_RIGHT = 1
local DIR_ID_UPRIGHT = 2
local DIR_ID_UP = 3
local DIR_ID_UPLEFT = 4
local DIR_ID_LEFT = 5
local DIR_ID_DOWNLEFT = 6
local DIR_ID_DOWN = 7
local DIR_ID_DOWNRIGHT = 8
function getNeighbor(point, dir_id)
return mapGet(
particleMap,
point.X + neighbor_dirs[dir_id].X,
point.Z + neighbor_dirs[dir_id].Z
)
end
function stepSimulation()
--Update velocities based on which neighbor positions are empty
local ndirs = neighbor_dirs
for _, particle in pairs(particles) do
local pos = particle.Position
if not getNeighbor(pos, DIR_ID_DOWN) then --Try moving down
if particle.Position.Z > -100 then
particle.Velocity = neighbor_dirs[DIR_ID_DOWN]
else
--Don't move down if far below the ground (???)
particle.Velocity = neighbor_dirs[DIR_ID_UP] --actually, move it up???
end
elseif (not getNeighbor(pos, DIR_ID_LEFT)) and (not getNeighbor(pos, DIR_ID_DOWNLEFT)) then --Try moving down to the left
particle.Velocity = neighbor_dirs[DIR_ID_DOWNLEFT]
elseif (not getNeighbor(pos, DIR_ID_RIGHT)) and (not getNeighbor(pos, DIR_ID_DOWNRIGHT)) then --Try moving down to the right
particle.Velocity = neighbor_dirs[DIR_ID_DOWNRIGHT]
else --Everything is blocked, stand still
particle.Velocity = Vector3.new(0, 0, 0)
end
end
--Update position
for _, particle in pairs(particles) do
if particle.Velocity.X ~= 0 or particle.Velocity.Z ~= 0 then --Don't move if standing still
local new_pos = particle.Position + particle.Velocity
if not mapGet( particleMap, new_pos.X, new_pos.Z ) then --Don't move if destination is blocked
mapSet(particleMap, particle.Position.X, particle.Position.Z, nil)
particle.Position = new_pos
mapSet(particleMap, particle.Position.X, particle.Position.Z, particle)
end
end
end
end
It doesn’t seem to work correctly, but it’s fairly easy to tweak the rules so you can get it to work like you want. Hope this helps!