From 8cc342e199b4d48ab61aa409b7ee6828e2ccd649 Mon Sep 17 00:00:00 2001 From: snoutie Date: Thu, 16 Jan 2025 22:38:26 +0100 Subject: [PATCH] Reworked Physics --- init.lua | 52 ++++--- internal.lua | 6 +- physics.lua | 422 +++++++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 432 insertions(+), 48 deletions(-) diff --git a/init.lua b/init.lua index d5e2136..2610df0 100644 --- a/init.lua +++ b/init.lua @@ -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") diff --git a/internal.lua b/internal.lua index 013ef17..f8499d6 100644 --- a/internal.lua +++ b/internal.lua @@ -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 diff --git a/physics.lua b/physics.lua index 93d70e7..8778590 100644 --- a/physics.lua +++ b/physics.lua @@ -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)