Compare commits

...

1 Commits

Author SHA1 Message Date
8cc342e199 Reworked Physics 2025-01-16 22:38:26 +01:00
3 changed files with 432 additions and 48 deletions

View File

@ -37,28 +37,40 @@ else
end
core.register_on_placenode(function(pos, newnode, placer, oldnode, itemstack, pointed_thing)
if internal.get_liquid_id(core.get_node(pos).name) ~= nil then
internal.add_node_to_check(pos)
end
--core.chat_send_all("Placed AT: " .. dump(pos) .. "___")
-- if internal.get_liquid_id(core.get_node(pos).name) ~= nil then
-- internal.add_node_to_check(pos)
-- end
end)
core.register_lbm {
name = "liquid_physics:update",
nodenames = "group:liquid_physics",
run_at_every_load = true,
action = function(pos, node, dtime_s)
internal.add_node_to_check(pos)
end,
}
-- core.register_lbm {
-- name = "liquid_physics:update",
-- nodenames = "group:liquid_physics",
-- run_at_every_load = true,
-- action = function(pos, node, dtime_s)
-- --internal.add_node_to_check(pos)
-- core.set_node(pos, { name = "air" })
-- end,
-- }
core.register_abm({
nodenames = "group:liquid_physics",
neighbors = { "air" },
interval = 0.2,
chance = 0,
action = function(pos, node, active_object_count, active_object_count_wider)
internal.add_node_to_check(pos)
end
})
-- core.register_lbm {
-- name = "liquid_physics:update",
-- nodenames = "group:liquid_physics",
-- run_at_every_load = true,
-- action = function(pos, node, dtime_s)
-- --internal.add_node_to_check(pos)
-- core.set_node(pos, { name = "air" })
-- end,
-- }
-- core.register_abm({
-- nodenames = "group:liquid_physics",
-- neighbors = { "air" },
-- interval = 0.2,
-- chance = 0,
-- action = function(pos, node, active_object_count, active_object_count_wider)
-- --internal.add_node_to_check(pos)
-- end
-- })
dofile(modpath .. "/physics.lua")

View File

@ -83,6 +83,9 @@ function internal.new_lpn(hash, pos)
liquid_id = liquid_id,
init_liquid_level = liquid_level,
liquid_level = liquid_level,
density = 0,
gradient = { x = 0, y = 0, z = 0 },
neighbors = {},
}
end
@ -106,7 +109,8 @@ end
-- @param pos table Position of the node
function internal.add_node_to_check(pos)
local h = core.hash_node_position(pos)
liquid_physics._nodes_to_check[h] = pos
local lpn = internal.new_lpn(h, pos)
liquid_physics._nodes_to_check[h] = lpn
end
-- This node will no longer be checked

View File

@ -17,12 +17,30 @@ local modpath = core.get_modpath(core.get_current_modname())
local internal = dofile(modpath .. "/internal.lua")
-- A table of offsets for ease of use
local offsets = {
{ x = 0, z = 1, },
{ x = 0, z = -1, },
{ x = 1, z = 0, },
{ x = -1, z = 0, }
}
local offsets = {}
-- local offsets = {
-- { x = 0, z = 0, y = 0, d = 0 },
-- { x = 0, z = 1, y = 0, d = 1 },
-- { x = 0, z = -1, y = 0, d = 1 },
-- { x = 1, z = 0, y = 0, d = 1 },
-- { x = -1, z = 0, y = 0, d = 1 },
-- }
for x = -1, 1 do
for y = -1, 1 do
for z = -1, 1 do
local d = math.abs(x) + math.abs(y) + math.abs(z)
table.insert(offsets, {
x = x, -- X Offset
y = y, -- Y Offset
z = z, -- Z Offset
d = d -- Distance to 0 / 0 / 0
})
end
end
end
-- Store number of offsets for less calculations
local number_of_offsets = table.getn(offsets)
@ -33,8 +51,8 @@ local number_of_offsets = table.getn(offsets)
local function add_offset_to_pos(pos, offset)
return {
x = pos.x + offset.x,
y = pos.y + offset.y,
z = pos.z + offset.z,
y = pos.y
}
end
@ -52,6 +70,16 @@ local function get_lpn_buffered(b, pos)
return b[h]
end
local function round_away_from_zero(value)
if value == 0 then
return 0
end
if value > 0 then
return math.ceil(value)
end
return math.floor(value) -- Let's hope math floor is optimized for zero
end
-- Checks if liquid_id of curr and lpn are the same.
-- Checks if the liquid_level is above -1
-- @param curr table The current LPN
@ -61,25 +89,203 @@ local function is_lpn_relevant(curr, lpn)
return lpn.liquid_level >= 0 and (lpn.liquid_level == 0 or curr.liquid_id == lpn.liquid_id)
end
-- local function get_gradient(base, compare, opp)
-- local compare_copy = compare
-- if compare_copy < 0 then compare_copy = 0 end
-- return math.ceil(((opp + base) - compare_copy * 2) / 8)
-- end
-- Gets all adjacent liquid nodes with the same liquid_id as curr
-- Gets all adjacent air nodes
-- @param b table Buffered LPN
-- @param curr table The current LPN
-- @return table Table of neighboring LPN
-- @return number Number of neighbors
local function get_valid_neighbors(b, curr)
local function get_all_neighbors_pressure(b, curr)
local neighbors = {}
local number_of_neighbors = 0
local no_of_h_neighbors = 0
local no_of_v_neighbors = 0
for i = 1, number_of_offsets do
local pos = add_offset_to_pos(curr.pos, offsets[i])
local current_offset = offsets[i]
local pos = add_offset_to_pos(curr.pos, current_offset)
local lpn = get_lpn_buffered(b, pos)
if is_lpn_relevant(curr, lpn) then
number_of_neighbors = number_of_neighbors + 1
table.insert(neighbors, math.random(number_of_neighbors, 1), lpn)
--if is_lpn_relevant(curr, lpn) then
local o_off = {}
if i % 2 == 0 then
o_off = offsets[i - 1]
else
o_off = offsets[i + 1]
end
local o_pos = add_offset_to_pos(curr.pos, o_off)
local o_lpn = get_lpn_buffered(b, o_pos)
local g = current_offset.g + get_gradient(curr.liquid_level, lpn.liquid_level, o_lpn.liquid_level)
if g ~= 0 then
if i < 5 then
no_of_h_neighbors = no_of_h_neighbors + 1
else
no_of_v_neighbors = no_of_v_neighbors + 1
end
end
table.insert(neighbors, { lpn = lpn, g = g })
--end
end
return neighbors, no_of_h_neighbors, no_of_v_neighbors
end
local function get_pressure_gradient(b, lpn)
local number_of_directions = 0
lpn.neighbors = get_all_neighbors_pressure(b, lpn)
lpn.pressure_gradient = internal.get_inital_pressure_gradient()
local i = 1
for _, g in pairs(lpn.pressure_gradient) do
g = g + get_gradient(lpn.liquid_level, lpn.neighbors[i].liquid_level)
i = i + 1
if g ~= 0 then
number_of_directions = number_of_directions + 1
end
end
return neighbors, number_of_neighbors
return number_of_directions
end
local function get_density_influence(r, d)
local vol = math.pi * (r ^ 8) / 4
local v = math.max(0, r - d)
return v + v * v / vol
end
local function get_density_influence_derivative(r, d)
if (d >= r) then return 0 end
local f = r * r - d * d
local scale = -24 / (math.pi * (r ^ 8))
return scale * d * f * f
end
local function get_density(b, lpn)
local radius = 3 -- 3 is max
for i = 1, number_of_offsets do
local offset = offsets[i]
local o_pos = add_offset_to_pos(lpn.pos, offset)
local o_lpn = get_lpn_buffered(b, o_pos)
local liquid_level = o_lpn.liquid_level
if liquid_level < 0 then
liquid_level = lpn.liquid_level
elseif liquid_level == 0 then
liquid_level = 0
end
-- if offset.d == 0 then
-- liquid_level = 0
-- end
local influence = liquid_level * get_density_influence(radius, offset.d)
table.insert(lpn.neighbors, { lpn = o_lpn, prs = 0 })
lpn.density = lpn.density + influence
end
lpn.density = math.floor(lpn.density)
end
local function get_pressure_from_density(density)
local multiplier = 1
local target_pressure = 11
--core.chat_send_all(density)
return (density - target_pressure) * multiplier
end
local function get_gradient(b, lpn)
-- local gradient = {
-- x = 0,
-- y = 0,
-- z = 0,
-- }
local radius = 3 -- 3 is max
for i = 1, number_of_offsets do
local offset = offsets[i]
if offset.d == 0 then
goto continue
end
local neighbor = lpn.neighbors[i].lpn
local density = neighbor.density
-- if density < 0 then
-- density = neighbor.density
-- else
if density == 0 then
density = 1
end
local slope = get_density_influence_derivative(radius, offset.d)
local pressure = get_pressure_from_density(density)
--if offset.y == 0 then
lpn.neighbors[i].prs = math.ceil(math.abs(
(pressure * slope * lpn.liquid_level / density)))
lpn.neighbors[i].is_down = false
if offset.y == 1 then
lpn.neighbors[i].prs = lpn.neighbors[i].prs - 6
end
if offset.y == -1 then
lpn.neighbors[i].prs = 1
lpn.neighbors[i].is_down = true
end
-- lpn.gradient = {
-- x = lpn.gradient.x + (pressure * offset.x * slope * neighbor.liquid_level / density),
-- y = lpn.gradient.y + (pressure * offset.y * slope * neighbor.liquid_level / density),
-- z = lpn.gradient.z + (pressure * offset.z * slope * neighbor.liquid_level / density),
-- }
::continue::
end
--core.chat_send_all("Pressure: " .. dump(lpn.gradient))
-- lpn.gradient = {
-- x = round_away_from_zero(lpn.gradient.x),
-- y = round_away_from_zero(lpn.gradient.y),
-- z = round_away_from_zero(lpn.gradient.z),
-- }
-- if lpn.gradient.x > 0 then
-- lpn.gradient.x = 1
-- elseif lpn.gradient.x < 0 then
-- lpn.gradient.x = -1
-- end
-- if lpn.gradient.y > 0 then
-- lpn.gradient.y = 1
-- elseif lpn.gradient.y < 0 then
-- lpn.gradient.y = -1
-- end
-- if lpn.gradient.z > 0 then
-- lpn.gradient.z = 1
-- elseif lpn.gradient.z < 0 then
-- lpn.gradient.z = -1
-- end
end
local function get_pressure_force(lpn)
end
-- Gets neighbors of curr and calculates the pressure
@ -127,6 +333,9 @@ local function try_move(from, to, amount)
if to.liquid_level >= 8 then
return 0
end
if amount <= 0 then
return 0
end
local max_allowed = amount + 8 - (to.liquid_level + amount)
local max_allowed_clamped = math.min(max_allowed, amount, from.liquid_level)
@ -212,20 +421,179 @@ local function move(b, curr_pos)
end
end
core.register_on_mapblocks_changed(function(modified_blocks, modified_blocks_count)
-- Buffered LPN
local b = {}
for hpos, pos in pairs(liquid_physics._nodes_to_check) do
if core.compare_block_status(pos, "active") then
move(b, pos)
else
internal.remove_node_to_check(pos)
end
local function try_spread(buffer, from, to, is_down)
if is_lpn_relevant(from, to) and ((from.liquid_level > 1 and from.liquid_level >= to.liquid_level + 1) or is_down) then
return try_move(from, to, 1)
end
-- Update only changed nodes from buffer
for _, lpn in pairs(b) do
if lpn.init_liquid_level ~= lpn.liquid_level then
internal.set_node(lpn)
return 0
end
local function shuffle(array)
math.randomseed(os.time()) -- Seed the random number generator
for i = #array, 2, -1 do
local j = math.random(i) -- Pick a random index from 1 to i
array[i], array[j] = array[j], array[i] -- Swap elements
end
end
local last_finish = 0
local active_blocks = {}
core.register_on_liquid_transformed(function(pos_list, node_list)
for hash, block_start in pairs(active_blocks) do
if core.compare_block_status(block_start, "active") then
else
active_blocks[hash] = nil
goto continue
end
--core.chat_send_all(dump(block_start) .. "----" .. modified_blocks_count)
local buffer = {}
local block_end = {
x = block_start.x + 15,
y = block_start.y + 15,
z = block_start.z + 15,
}
local node_pos_list = core.find_nodes_in_area(block_start, block_end, { "group:liquid_physics" })
local lpn_list = {}
local number_of_lpn = 0
for _, pos in pairs(node_pos_list) do
local lpn = get_lpn_buffered(buffer, pos)
get_density(buffer, lpn)
table.insert(lpn_list, lpn)
number_of_lpn = number_of_lpn + 1
end
for i = 1, number_of_lpn do
local g_lpn = lpn_list[i]
get_gradient(buffer, g_lpn)
--g_lpn.gradient.y = 0
--core.chat_send_all("Grad: " .. dump(g_lpn.gradient))
-- local spread_to_pos = add_offset_to_pos(g_lpn.pos, g_lpn.gradient)
-- local spread_to = get_lpn_buffered(buffer, spread_to_pos)
-- try_spread(buffer, g_lpn, spread_to)
-- local gs = 0
-- for g = 1, number_of_offsets do
-- if g_lpn.neighbors[g].prs > 0 then
-- gs = gs + 1
-- end
-- end
local number_of_targets = 0
for g = 1, number_of_offsets do
if g_lpn.neighbors[g].prs > 0 and not g_lpn.neighbors[g].is_down then
number_of_targets = number_of_targets + 1
end
end
if number_of_targets >= g_lpn.liquid_level then
shuffle(g_lpn.neighbors)
end
for g = 1, number_of_offsets do
if g_lpn.neighbors[g].prs > 0 then
try_spread(buffer, g_lpn, g_lpn.neighbors[g].lpn, g_lpn.neighbors[g].is_down)
end
end
-- if g_lpn.gradient.x ~= 0 then
-- local spread_to_pos = add_offset_to_pos(g_lpn.pos, { x = g_lpn.gradient.x, y = 0, z = 0 })
-- local spread_to = get_lpn_buffered(buffer, spread_to_pos)
-- try_spread(buffer, g_lpn, spread_to)
-- -- if try_spread(buffer, g_lpn, spread_to, gs) == 0 then
-- -- spread_to_pos = add_offset_to_pos(g_lpn.pos, { x = g_lpn.gradient.x * -1, y = 0, z = 0 })
-- -- spread_to = get_lpn_buffered(buffer, spread_to_pos)
-- -- try_spread(buffer, g_lpn, spread_to, gs)
-- -- end
-- end
-- if g_lpn.gradient.z ~= 0 then
-- local spread_to_pos = add_offset_to_pos(g_lpn.pos, { x = 0, y = 0, z = g_lpn.gradient.z })
-- local spread_to = get_lpn_buffered(buffer, spread_to_pos)
-- try_spread(buffer, g_lpn, spread_to)
-- -- if try_spread(buffer, g_lpn, spread_to, gs) == 0 then
-- -- spread_to_pos = add_offset_to_pos(g_lpn.pos, { x = 0, y = 0, z = g_lpn.gradient.z * -1 })
-- -- spread_to = get_lpn_buffered(buffer, spread_to_pos)
-- -- try_spread(buffer, g_lpn, spread_to, gs)
-- -- end
-- end
end
if lpn_list[1] ~= nil then
end
-- for _, pos in pairs(node_pos_list) do
-- local lpn = get_lpn_buffered(buffer, pos)
-- local neighbors, h, v = get_all_neighbors_pressure(buffer, lpn)
-- local n = 0
-- for i = 1, number_of_offsets do
-- if neighbors[i].g == 0 then
-- goto continue
-- end
-- if i < 5 then
-- n = h
-- else
-- n = v - 1
-- end
-- try_spread(buffer, lpn, neighbors[i].lpn, n, neighbors[i].g)
-- ::continue::
-- end
-- end
-- ::continue::
--
for _, lpn in pairs(buffer) do
if lpn.init_liquid_level ~= lpn.liquid_level then
if lpn.liquid_id then
internal.set_node(lpn)
end
end
end
::continue::
end
end)
core.register_on_mapblocks_changed(function(modified_blocks, modified_blocks_count)
for hash, _ in pairs(modified_blocks) do
local block_start = core.get_position_from_hash(hash)
block_start = {
x = block_start.x * 16,
y = block_start.y * 16,
z = block_start.z * 16,
}
if core.compare_block_status(block_start, "active") then
active_blocks[hash] = block_start
else
active_blocks[hash] = nil
end
end
-- -- Buffered LPN
-- local b = {}
-- for hpos, pos in pairs(liquid_physics._nodes_to_check) do
-- if core.compare_block_status(pos, "active") then
-- move(b, pos)
-- else
-- internal.remove_node_to_check(pos)
-- end
-- end
-- -- Update only changed nodes from buffer
-- for _, lpn in pairs(b) do
-- if lpn.init_liquid_level ~= lpn.liquid_level then
-- internal.set_node(lpn)
-- end
-- end
end)