236 lines
No EOL
8.6 KiB
Lua
236 lines
No EOL
8.6 KiB
Lua
|
|
local REALM_START = vector.new(-100, 5000, -100)
|
|
local REALM_END = vector.new(100, 5500, 100)
|
|
|
|
local vm_data = {}
|
|
local area
|
|
|
|
local function intersection(min, max, b, c)
|
|
return min.x < c.x and max.x > b.x and
|
|
min.y < c.y and max.y > b.y and
|
|
min.z < c.z and max.z > b.z
|
|
end
|
|
|
|
local c_air = minetest.get_content_id("air")
|
|
|
|
local c_stone = minetest.get_content_id("stone")
|
|
local c_cobble = minetest.get_content_id("cobble")
|
|
local c_mossy_cobble = minetest.get_content_id("cobble_mossy")
|
|
local c_dirt = minetest.get_content_id("dirt")
|
|
local c_stony_dirt = minetest.get_content_id("dirt_stony")
|
|
local c_mossy_dirt = minetest.get_content_id("dirt_mossy")
|
|
local c_grass = minetest.get_content_id("dirt_grass")
|
|
|
|
local c_planks = minetest.get_content_id("outback_planks")
|
|
local c_wood = c_planks
|
|
local c_leaves = c_stone
|
|
|
|
|
|
local np_terrain = {
|
|
offset = 0,
|
|
scale = 1, -- Small height variation
|
|
spread = {x = 64, y = 64, z = 64},
|
|
seed = 12345,
|
|
octaves = 4,
|
|
persist = 0.6
|
|
}
|
|
local n_terrain = {}
|
|
|
|
local np_surface = {
|
|
offset = 0,
|
|
scale = 1, -- Small height variation
|
|
spread = {x = 4, y = 4, z = 4},
|
|
seed = 87745,
|
|
octaves = 3 ,
|
|
persist = 0.6
|
|
}
|
|
local n_surface = {}
|
|
|
|
local np_trees = {
|
|
offset = 0,
|
|
scale = 1, -- Small height variation
|
|
spread = {x = 64, y = 64, z = 64},
|
|
seed = 24521,
|
|
octaves = 4,
|
|
persist = 0.6
|
|
}
|
|
local n_trees = {}
|
|
|
|
|
|
local np_canopy = {
|
|
offset = 0,
|
|
scale = 0.5,
|
|
spread = {x=16, y=16, z=16},
|
|
seed = 91527, -- Different seed for variety
|
|
octaves = 2,
|
|
persist = 0.5
|
|
}
|
|
local n_canopy = {} -- Buffer for 3D noise; generate once per chunk if needed
|
|
|
|
local function generate_tree(min, max, base_x, base_y, base_z)
|
|
-- Randomize tree "type" for variation
|
|
local tree_type = math.random() -- 0-1
|
|
local trunk_height = math.floor(8 + tree_type * 7) -- 8-15 tall
|
|
local canopy_radius = 3 + math.floor(tree_type * 2) -- 3-5 wide
|
|
local trunk_width = 1 + (tree_type > 0.7 and 1 or 0) -- Mostly 1-wide, sometimes 2
|
|
|
|
local top_y = base_y + trunk_height
|
|
|
|
-- 1. Roots: Flared base, 2-3 deep, random protrusions
|
|
local root_depth = 2 + math.random(1)
|
|
for dy = -root_depth, 0 do
|
|
local radius_sq = (dy == 0 and 2 or 1)^2 -- Wider at surface
|
|
for dx = -2, 2 do
|
|
for dz = -2, 2 do
|
|
local dist_sq = dx*dx + dz*dz
|
|
if dist_sq <= radius_sq and math.random() > 0.6 then -- ~40% fill for irregularity
|
|
local py = base_y + dy
|
|
if py >= min.y and py <= max.y then -- Bounds check
|
|
local vi = area:index(base_x + dx, py, base_z + dz)
|
|
-- Only set if it's grass/dirt (don't overwrite stone)
|
|
if vm_data[vi] == c_grass or vm_data[vi] == c_dirt then
|
|
vm_data[vi] = c_wood
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 2. Trunk: Vertical core, optional width
|
|
for py = base_y, top_y do
|
|
if py >= min.y and py <= max.y then
|
|
-- Central trunk
|
|
local trunk_vi = area:index(base_x, py, base_z)
|
|
vm_data[trunk_vi] = c_wood
|
|
|
|
-- Optional side for width (3-4 effective with roots)
|
|
if trunk_width == 2 and math.random() > 0.5 then
|
|
local side_dx = (math.random() > 0.5 and 1 or -1)
|
|
local side_dz = (math.random() > 0.5 and 1 or -1)
|
|
local side_vi = area:index(base_x + side_dx, py, base_z + side_dz)
|
|
vm_data[side_vi] = c_wood
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 3. Canopy: Irregular spreading blob
|
|
-- Simple 3D loop for ellipsoid; modulate with noise for gaps/branches
|
|
local canopy_sides3d = {x = max.x - min.x + 1, y = max.y - min.y + 1, z = max.z - min.z + 1}
|
|
-- local canopy_map = minetest.get_perlin_map(np_canopy, canopy_sides3d)
|
|
local canopy_noise = minetest.get_perlin_map(np_canopy, canopy_sides3d)
|
|
canopy_noise:get_3d_map_flat({x=base_x, y=base_y, z=base_z}, n_canopy) -- Sample at base
|
|
local noise_val = n_canopy[1]
|
|
|
|
for dy = -2, canopy_radius do -- Vertical spread
|
|
local py = top_y + dy - 1 -- Start 1 below top, extend up/down
|
|
if py >= min.y and py <= max.y then
|
|
local y_scale = 1 - math.abs(dy) / canopy_radius -- Taper vertically
|
|
local horiz_radius = math.floor(canopy_radius * y_scale * (0.7 + noise_val * 0.3)) -- Noise variation
|
|
|
|
for dx = -canopy_radius, canopy_radius do
|
|
for dz = -canopy_radius, canopy_radius do
|
|
local dist_sq = dx*dx + dz*dz
|
|
local required_radius_sq = horiz_radius * horiz_radius
|
|
|
|
if dist_sq <= required_radius_sq then
|
|
-- Noise for branch-like gaps (lower density = more holes)
|
|
local branch_noise = (noise_val + math.random(-0.2, 0.2)) * 0.5
|
|
if math.random() < (0.7 + branch_noise) then -- 50-90% fill
|
|
local vi = area:index(base_x + dx, py, base_z + dz)
|
|
-- Overwrite air/grass only
|
|
-- if vm_data[vi] == 0 or vm_data[vi] == c_grass then -- 0 = air
|
|
vm_data[vi] = c_leaves
|
|
-- end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- 4. Optional branches: Quick stubs from trunk for extra realism
|
|
local num_branches = 2 + math.random(3)
|
|
for i = 1, num_branches do
|
|
local branch_y = base_y + math.random(4, trunk_height - 2)
|
|
local branch_len = 1 + math.random(3)
|
|
local dir_x, dir_z = 0, 0
|
|
if math.random() > 0.5 then dir_x = (math.random() > 0.5 and 1 or -1) * branch_len
|
|
else dir_z = (math.random() > 0.5 and 1 or -1) * branch_len end
|
|
-- Draw line of wood (simple, no Bresenham for perf)
|
|
for step = 0, branch_len do
|
|
local px = base_x + math.floor(dir_x * step / branch_len)
|
|
local pz = base_z + math.floor(dir_z * step / branch_len)
|
|
local py = branch_y + math.floor((top_y - branch_y) * step / trunk_height * 0.3) -- Slight upward curve
|
|
local vi = area:index(px, py, pz)
|
|
if vm_data[vi] == 0 or vm_data[vi] == c_grass then
|
|
vm_data[vi] = c_wood
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function tree(min, max, x, y, z)
|
|
|
|
end
|
|
|
|
|
|
minetest.register_on_generated(function(vm, min, max)
|
|
-- Only run for blocks that are part of this realm.
|
|
if not intersection(min, max, REALM_START, REALM_END) then return end
|
|
area = VoxelArea(vm:get_emerged_area())
|
|
vm:get_data(vm_data)
|
|
|
|
local sides2d = {x = max.x - min.x + 1, y = max.z - min.z + 1}
|
|
local terrain = minetest.get_perlin_map(np_terrain, sides2d)
|
|
terrain:get_2d_map_flat({x = min.x, y = min.z}, n_terrain)
|
|
|
|
local surface = minetest.get_perlin_map(np_surface, sides2d)
|
|
surface:get_2d_map_flat({x = min.x, y = min.z}, n_surface)
|
|
|
|
local trees_map = minetest.get_perlin_map(np_trees, sides2d)
|
|
trees_map:get_2d_map_flat({x = min.x, y = min.z}, n_trees)
|
|
|
|
local trees = {}
|
|
|
|
local ni = 1
|
|
for z = min.z, max.z do
|
|
for x = min.x, max.x do
|
|
--Calculate heightmap
|
|
|
|
local height = REALM_START.y +100 +(n_terrain[ni] *5)
|
|
|
|
for y = min.y, max.y do
|
|
if y > REALM_START.y and y < REALM_END.y then
|
|
local vi = area:index(x, y, z)
|
|
if y < height -1 then
|
|
vm_data[vi] = c_stone
|
|
elseif y < height then
|
|
if n_surface[ni] > 0.65 then
|
|
vm_data[vi] = c_mossy_cobble
|
|
elseif n_surface[ni] > 0.3 then
|
|
vm_data[vi] = c_cobble
|
|
elseif n_surface[ni] > -0.3 then
|
|
vm_data[vi] = c_stony_dirt
|
|
elseif n_surface[ni] < -0.8 then
|
|
vm_data[vi] = c_mossy_dirt
|
|
else
|
|
vm_data[vi] = c_dirt
|
|
end
|
|
elseif y < height +1 and math.random() > 0.98 then
|
|
trees[#trees +1] = vector.new(x, y, z)
|
|
else
|
|
vm_data[vi] = c_air
|
|
end
|
|
end
|
|
end
|
|
ni = ni +1
|
|
end
|
|
end
|
|
|
|
for _, x in pairs(trees) do
|
|
tree(min, max, x.x, x.y, x.z)
|
|
end
|
|
|
|
vm:set_data(vm_data)
|
|
end) |