2020-08-20 02:53:26 +02:00
2020-08-21 21:59:50 +02:00
-- Test command: //multi //fp set1 1313 6 5540 //fp set2 1338 17 5521 //erode snowballs
2020-08-21 16:21:10 +02:00
2020-08-20 02:53:26 +02:00
local function snowball ( heightmap , normalmap , heightmap_size , startpos , params )
local sediment = 0
local pos = { x = startpos.x , z = startpos.z }
local pos_prev = { x = pos.x , z = pos.z }
2020-08-21 21:59:50 +02:00
local velocity = {
x = ( math.random ( ) * 2 - 1 ) * params.init_velocity ,
z = ( math.random ( ) * 2 - 1 ) * params.init_velocity
}
2020-08-20 02:53:26 +02:00
local heightmap_length = # heightmap
2020-08-21 21:59:50 +02:00
-- print("[snowball] startpos ("..pos.x..", "..pos.z.."), velocity: ("..velocity.x..", "..velocity.z..")")
2020-08-21 16:21:10 +02:00
2020-08-21 21:59:50 +02:00
local hist_velocity = { }
for i = 1 , params.max_steps do
2020-08-21 16:21:10 +02:00
local x = pos.x
local z = pos.z
2021-02-26 03:20:53 +01:00
local hi = math.floor ( z + 0.5 ) * heightmap_size.x + math.floor ( x + 0.5 )
2020-08-20 02:53:26 +02:00
-- Stop if we go out of bounds
2020-08-21 16:21:10 +02:00
if x < 0 or z < 0
2021-02-26 03:20:53 +01:00
or x >= heightmap_size.x - 1 or z >= heightmap_size.z - 1 then
-- print("[snowball] hit edge; stopping at ("..x..", "..z.."), (bounds @ "..(heightmap_size.x-1)..", "..(heightmap_size.z-1)..")", "x", x, "/", heightmap_size.x-1, "z", z, "/", heightmap_size.z-1)
2020-08-21 21:59:50 +02:00
return true , i
end
if # hist_velocity > 0 and i > 5
and worldeditadditions.average ( hist_velocity ) < 0.03 then
-- print("[snowball] It looks like we've stopped")
return true , i
2020-08-20 02:53:26 +02:00
end
2020-08-21 21:59:50 +02:00
if normalmap [ hi ] . y == 1 then return true , i end
if hi > heightmap_length then return false , " Out-of-bounds on the array, hi: " .. hi .. " , heightmap_length: " .. heightmap_length end
2020-08-21 16:21:10 +02:00
2020-08-21 22:00:45 +02:00
-- NOTE: We need to decide whether we want to keep the precomputed normals as we have now, or whether we want to dynamically compute them at the some of request.
2020-08-21 16:21:10 +02:00
-- print("[snowball] sediment", sediment, "rate_deposit", params.rate_deposit, "normalmap[hi].z", normalmap[hi].z)
2020-08-20 02:53:26 +02:00
local step_deposit = sediment * params.rate_deposit * normalmap [ hi ] . z
local step_erode = params.rate_erosion * ( 1 - normalmap [ hi ] . z ) * math.min ( 1 , i * params.scale_iterations )
-- Erode / Deposit, but only if we are on a different node than we were in the last step
2020-08-21 16:21:10 +02:00
if math.floor ( pos_prev.x ) ~= math.floor ( x )
and math.floor ( pos_prev.z ) ~= math.floor ( z ) then
2020-08-21 21:59:50 +02:00
heightmap [ hi ] = heightmap [ hi ] + ( step_deposit - step_erode )
2020-08-20 02:53:26 +02:00
end
velocity.x = params.friction * velocity.x + normalmap [ hi ] . x * params.speed
velocity.z = params.friction * velocity.z + normalmap [ hi ] . y * params.speed
2020-08-21 21:59:50 +02:00
-- print("[snowball] now at ("..x..", "..z..") velocity "..worldeditadditions.vector.lengthsquared(velocity)..", sediment "..sediment)
local new_vel_sq = worldeditadditions.vector . lengthsquared ( velocity )
if new_vel_sq > 1 then
-- print("[snowball] velocity squared over 1, normalising")
velocity = worldeditadditions.vector . normalize ( velocity )
end
table.insert ( hist_velocity , new_vel_sq )
if # hist_velocity > params.velocity_hist_count then table.remove ( hist_velocity , 1 ) end
2020-08-21 16:21:10 +02:00
pos_prev.x = x
pos_prev.z = z
2020-08-20 02:53:26 +02:00
pos.x = pos.x + velocity.x
pos.z = pos.z + velocity.z
2020-08-21 21:59:50 +02:00
sediment = sediment + ( step_erode - step_deposit ) -- Needs to be erosion - deposit, which is the opposite to the above
2020-08-20 02:53:26 +02:00
end
2020-08-21 21:59:50 +02:00
return true , params.max_steps
2020-08-18 03:11:37 +02:00
end
2020-08-21 14:27:40 +02:00
--[[
2 D erosion algorithm based on snowballs
Note that this * mutates * the given heightmap .
@ source https : // jobtalle.com / simulating_hydraulic_erosion.html
] ] --
2020-08-21 21:59:50 +02:00
function worldeditadditions . erode . snowballs ( heightmap_initial , heightmap , heightmap_size , region_height , params_custom )
local params = {
rate_deposit = 0.03 , -- 0.03
rate_erosion = 0.04 , -- 0.04
2020-08-21 14:27:40 +02:00
friction = 0.07 ,
2020-08-21 21:59:50 +02:00
speed = 1 ,
max_steps = 80 ,
velocity_hist_count = 3 ,
init_velocity = 0.25 ,
2020-08-21 14:27:40 +02:00
scale_iterations = 0.04 ,
2020-08-21 21:59:50 +02:00
maxdiff = 0.4 ,
2020-08-21 23:01:24 +02:00
count = 25000
2020-08-21 21:59:50 +02:00
}
-- Apply the default settings
worldeditadditions.table_apply ( params_custom , params )
2020-08-21 23:01:24 +02:00
-- print("[erode/snowballs] params: ")
-- print(worldeditadditions.map_stringify(params))
2020-08-21 16:21:10 +02:00
2020-08-21 14:27:40 +02:00
local normals = worldeditadditions.calculate_normals ( heightmap , heightmap_size )
2020-08-21 21:59:50 +02:00
local stats_steps = { }
for i = 1 , params.count do
-- print("[snowballs] starting snowball ", i)
local success , steps = snowball (
2020-08-21 14:27:40 +02:00
heightmap , normals , heightmap_size ,
2020-08-21 16:21:10 +02:00
{
2021-02-26 03:20:53 +01:00
x = math.random ( ) * ( heightmap_size.x - 1 ) ,
z = math.random ( ) * ( heightmap_size.z - 1 )
2020-08-21 16:21:10 +02:00
} ,
params
2020-08-21 14:27:40 +02:00
)
2020-08-21 21:59:50 +02:00
table.insert ( stats_steps , steps )
if not success then return false , " Error: Failed at snowball " .. i .. " : " .. steps end
2020-08-21 14:27:40 +02:00
end
2020-08-21 16:21:10 +02:00
2020-08-21 23:01:24 +02:00
-- print("[snowballs] "..#stats_steps.." snowballs simulated, max "..params.max_steps.." steps, averaged ~"..worldeditadditions.average(stats_steps).."")
2020-08-21 21:59:50 +02:00
2020-08-21 16:21:10 +02:00
-- Round everything to the nearest int, since you can't really have
-- something like .141592671 of a node
-- Note that we do this after *all* the erosion is complete
2020-08-21 21:59:50 +02:00
local clamp_limit = math.floor ( region_height * params.maxdiff + 0.5 )
2020-08-21 16:21:10 +02:00
for i , v in ipairs ( heightmap ) do
heightmap [ i ] = math.floor ( heightmap [ i ] + 0.5 )
2020-08-21 21:59:50 +02:00
if heightmap [ i ] < 0 then heightmap [ i ] = 0 end
-- Limit the distance to params.maxdiff% of the region height
if math.abs ( heightmap_initial [ i ] - heightmap [ i ] ) > region_height * params.maxdiff then
if heightmap_initial [ i ] - heightmap [ i ] > 0 then
heightmap [ i ] = heightmap_initial [ i ] - clamp_limit
else
heightmap [ i ] = heightmap_initial [ i ] + clamp_limit
end
end
2020-08-21 16:21:10 +02:00
end
2020-08-21 23:01:24 +02:00
if not params.noconv then
local success , matrix = worldeditadditions.get_conv_kernel ( " gaussian " , 3 , 3 )
if not success then return success , matrix end
matrix_size = { } matrix_size [ 0 ] = 3 matrix_size [ 1 ] = 3
worldeditadditions.conv . convolve (
heightmap , heightmap_size ,
matrix ,
matrix_size
)
end
2020-08-21 21:59:50 +02:00
2020-09-28 02:30:15 +02:00
return true , " " .. # stats_steps .. " snowballs simulated, max " .. params.max_steps .. " steps (averaged ~ " .. worldeditadditions.average ( stats_steps ) .. " steps) "
2020-08-21 14:27:40 +02:00
end