Planetoids/init.lua

836 lines
21 KiB
Lua

planetoids = {}
planetoids.layers = {}
--Layer def
-- name
-- string
-- dimensions
-- 2 or 3
-- block_size
-- vector - norm 5^3 or nil
-- scale
-- integer - the sector lengths are multiplied by this, but the
-- noise produced has a lower resolution
-- geometry
-- string - euclidean,manhattan,chebyshev
--
--Layer in mem
-- cache
-- table of tables
--
--
--Point in mem
-- pos
-- vector
--[[
--TODO list
--Docs
--add more types of noise - cubic cell noise especially
--]]
--functions defined in local scope for performance
local minetest = minetest
local abs = math.abs
local floor = math.floor
local hash_pos = minetest.hash_node_position
local air = minetest.get_content_id("air")
dofile(minetest.get_modpath("planetoids").."/distance.lua")
dofile(minetest.get_modpath("planetoids").."/maps.lua")
--normal vector.add has a check for b not being a table, I don't need this
local vector_add = function(a,b)
return {x=a.x+b.x,y=a.y+b.y,z=a.z+b.z}
end
--sector 0,0,0 has a smallest point at 0,0,0
local sector_to_pos = function(sector)
local lengths = planetoids.settings.sector_lengths
local pos = {}
pos.x = lengths.x * sector.x
pos.y = lengths.y * sector.y
pos.z = lengths.z * sector.z
return pos
end
--point 0,0,0 is in sector 0,0,0
local pos_to_sector = function(pos)
local lengths = planetoids.settings.sector_lengths
local sector = {x=pos.x,y=pos.y,z=pos.z}
sector.x = floor(sector.x/lengths.x)
sector.z = floor(sector.z/lengths.z)
sector.y = floor(sector.y/lengths.y)
return sector
end
local find_node_perlin = function(pos,points,dist_func,perlin)
local dist = nil
local node = nil
local set = planetoids.settings
for i=1,#points do
local point = points[i]
dist = dist_func(pos,point.pos)
if dist < point.radius then
local norm_dist = (dist*2)/point.radius - 1
local noise = perlin - norm_dist
if noise <= set.threshold then
return air
end
if point.ptype.crust_thickness then
local norm_crust = ((point.ptype.crust_thickness
+ set.thickness_offset)*2)/point.radius
if noise - norm_crust > set.threshold then
return point.ptype.filling_material
elseif point.ptype.crust_top_material
and pos.y >= point.pos.y then
return point.ptype.crust_top_material
else
return point.ptype.crust_material
end
else
return point.ptype.filling_material
end
end
end
return air
end
local find_node = function(pos,points,dist_func)
local dist = nil
local node = nil
for i=1,#points do
local point = points[i]
dist = dist_func(pos,point.pos)
if dist < point.radius then
if point.ptype.crust_thickness then
if dist < point.radius - point.ptype.crust_thickness then
return point.ptype.filling_material
elseif point.ptype.crust_top_material
and pos.y >= point.pos.y then
return point.ptype.crust_top_material
else
return point.ptype.crust_material
end
else
return point.ptype.filling_material
end
end
end
return air
end
--block locations must start at (0,0,0)
--for 2d use (x,y) rather than (x,0,z)
local blockfiller = function(blockdata,blocksize,table,tablesize,blockstart)
local tableit = blockstart
local ybuf,zbuf = tablesize.x - blocksize.x,(tablesize.y - blocksize.y)*tablesize.x
local x,y,z = 1,1,1
local blocklength = blocksize.x*blocksize.y*(blocksize.z or 1)
for i=1,blocklength do
if x > blocksize.x then
x = 1
y = y + 1
tableit = tableit + ybuf
end
if y > blocksize.y then
y = 1
z = z + 1
tableit = tableit + zbuf
end
table[tableit] = blockdata[i]
tableit = tableit + 1
x = x + 1
end
end
--Uses PcgRandom for better range - a 32 bit random would limit sector sizes to
-- 600^3 due to randomness issues
local generate_points = function(sector,seed)
local hash = hash_pos(sector)
local offset = planetoids.settings.seed_offset
local prand = PcgRandom(hash + (seed + offset) % 100000)
--Distribution is completely user defined
local point_dist = planetoids.settings.point_distribution
local num = prand:next(1,point_dist.rand_max)
local set = false
local cum = 0
for i=#point_dist,0,-1 do
if point_dist[i] then
cum = point_dist[i] + cum
if num <= cum then
num = i
set = true
break
end
end
end
--If no suitable number of points is found, 1 is set as a fallback
if not set then
num = planetoids.settings.planet_size.default
end
--Generate each point
local seen = {}
local points = {}
while num > 0 do
--The points are aligned to 0.1 of a block
--This used to be to 1 block, but having multiple points at
--the same distance was causing artifacts with the experimental gen
local get_dist = planetoids.settings.get_dist
local x = prand:next(0,planetoids.settings.sector_lengths.x-1)
local y = prand:next(0,planetoids.settings.sector_lengths.y-1)
local z = prand:next(0,planetoids.settings.sector_lengths.z-1)
local pos = {x=x,y=y,z=z}
local hashed = hash_pos(pos)
if not seen[hashed] then
pos = vector_add(pos,sector_to_pos(sector))
local radius = prand:next(planetoids.settings.planet_size.minimum,
planetoids.settings.planet_size.maximum)
local touching = false
for i,v in ipairs(points) do
if radius + v.radius > get_dist(pos,v.pos) then
touching = true
break
end
end
if not touching then
point = {pos = pos,radius = radius}
table.insert(points,point)
seen[hashed] = pos
end
end
num = num - 1
end
--The random number generator is returned for use in adding other
--properties to the points
return points , prand
end
--This is a wrapper around generate_points - this adds the planet type and doesn't return the random
--number generator
local generate_decorated_points = function(sector,seed)
local hash = hash_pos(sector)
--This is a cache for storing points that were already generated
--this should improve performance - but profiling breaks it
--[[
if planetoids.cache[hash] then
return planetoids.cache[hash]
end
--]]
local points,prand = generate_points(sector,seed)
local planet_types = planetoids.settings.planet_types
for i=1,#points do
local point = points[i]
--choose plaent type
local cum = 0
local ptype = prand:next(1,planet_types.rand_max)
local set = false
for i=#planet_types,1,-1 do
cum = planet_types[i].rarity + cum
if ptype <= cum then
ptype = planet_types[i]
set = true
break
end
end
--If no suitable number of points is found, 1 is set as a fallback
if not set then
ptype = planet_types[1]
end
--choose specific planet
local num = prand:next(1,ptype.rand_max)
local set = false
local cum = 0
for i=#ptype,1,-1 do
cum = ptype[i].rarity + cum
if num <= cum then
ptype = ptype[i]
set = true
break
end
end
--If no suitable number of points is found, 1 is set as a fallback
if not set then
ptype = ptype[1]
end
point.ptype = ptype
end
--planetoids.cache[hash] = points
return points
end
--removes points from sector which collide with those in comp
--tries to shrink first
local function point_remover(sector,comp)
local get_dist = planetoids.settings.get_dist
for index,point in ipairs(sector) do
for _,comp_point in ipairs(comp) do
local dist = get_dist(point.pos,comp_point.pos)
if comp_point.radius + point.radius > dist then
point.radius = dist - comp_point.radius - 2
if point.radius
< planetoids.settings.planet_size.minimum then
point.radius = -math.huge
end
end
end
end
--remove all invalid points
for index=#sector,1,-1 do
while (sector[index] and sector[index].radius == -math.huge) do
table.remove(sector,index)
end
end
end
--returns true is sector has a higher precience than comp
--or both are the same value
local function sector_precidence(sector,comp)
if sector.x ~= comp.x then
if sector.x % 2 == 0 then
return true
else
return false
end
elseif sector.y ~= comp.y then
if sector.y % 2 == 0 then
return true
else
return false
end
elseif sector.z ~= comp.z then
if sector.z % 2 == 0 then
return true
else
return false
end
end
return true
end
local function remove_collisions(sector,source)
local hash = hash_pos(sector)
local this_sector = source[hash]
local comp = {x=0,y=0,z=0}
for i=sector.x-1,sector.x+1 do
local hash_x = i + 32768
comp.x = i
for j=sector.y-1,sector.y+1 do
local hash_y = (j + 32768) * 65536
comp.y = j
for l=sector.z-1,sector.z+1 do
comp.z = l
local hash_z = (l + 32768) * 65536 * 65536
if not sector_precidence(sector,comp) then
local comp_sector = source[hash_z + hash_y + hash_x]
if comp_sector then
point_remover(this_sector,comp_sector)
end
end
end
end
end
end
local function generate_point_area(minp,maxp,seed)
local min_sector = pos_to_sector(minp)
local max_sector = pos_to_sector(maxp)
local sectors = {}
--populate table with sectors
for i=min_sector.x-2,max_sector.x+2 do
local hash_x = i + 32768
for j=min_sector.y-2,max_sector.y+2 do
local hash_y = (j + 32768) * 65536
for l=min_sector.z-2,max_sector.z+2 do
local hash_z = (l + 32768) * 65536 * 65536
sectors[hash_z + hash_y + hash_x] = generate_decorated_points({x=i,y=j,z=l},seed)
end
end
end
--Remove colliding points
for i=min_sector.x-1,max_sector.x+1 do
for j=min_sector.y-1,max_sector.y+1 do
for l=min_sector.z-1,max_sector.z+1 do
remove_collisions({x=i,y=j,z=l},sectors)
end
end
end
--return the table - still hashed for use in later functions
return sectors
end
local generate_block = function(blocksize,blockcentre,blockmin,seed,source,byot)
local points = {}
local block = byot or {}
local index = 1
local blockmax = {x=blockmin.x+(blocksize.x-1),y=blockmin.y+(blocksize.y -1)
,z=blockmin.z+(blocksize.z-1)}
local sector = pos_to_sector(blockcentre)
local get_dist = planetoids.settings.get_dist
local cube_max_dist = get_dist(blockmin,blockcentre)
-- points in moore raduis
local x,y,z = -1,-1,-1
for i=1,27 do
if x > 1 then
x = -1
y = y + 1
end
if y > 1 then
y = -1
z = z + 1
end
local temp = source[hash_pos({x=sector.x+x,y=sector.y+y,z=sector.z+z})]
for i,v in ipairs(temp) do
local dist = get_dist(blockcentre,v.pos)
if dist < v.radius + cube_max_dist then
points[index] = v
v.dist = dist
index = index + 1
end
end
x = x + 1
end
if #points == 0 then
local tablesize = blocksize.x*blocksize.y*blocksize.z
local x,y,z = blockmin.x,blockmin.y,blockmin.z
for i = 1,tablesize do
if x > blockmax.x then
x = blockmin.x
y = y + 1
end
if y > blockmax.y then
y = blockmin.y
z = z + 1
end
block[i] = air
x = x + 1
end
elseif planetoids.settings.mode == "perlin" then
local perlin_map = planetoids.perlin:get3dMap_flat(blockmin)
local tablesize = blocksize.x*blocksize.y*blocksize.z
local x,y,z = blockmin.x,blockmin.y,blockmin.z
local nixyz = 1
for i = 1,tablesize do
if x > blockmax.x then
x = blockmin.x
y = y + 1
end
if y > blockmax.y then
y = blockmin.y
z = z + 1
end
block[i] = find_node_perlin({x=x,y=y,z=z}
,points,get_dist,perlin_map[nixyz])
x = x + 1
nixyz = nixyz + 1
end
else
local tablesize = blocksize.x*blocksize.y*blocksize.z
local x,y,z = blockmin.x,blockmin.y,blockmin.z
for i = 1,tablesize do
if x > blockmax.x then
x = blockmin.x
y = y + 1
end
if y > blockmax.y then
y = blockmin.y
z = z + 1
end
block[i] = find_node({x=x,y=y,z=z}
,points,get_dist)
x = x + 1
end
end
return block
end
local shared_block_byot = {}
local function init_maps(minp,maxp)
local side_length = maxp.x-minp.x+1
planetoids.perlin = minetest.get_perlin_map(planetoids.settings.perlin_map,planetoids.settings.blocksize)
end
--map is generated in blocks
--this allows for distance testing to reduce the number of points to test
local get_planet_map = function(minp,maxp,seed,byot)
if not planetoids.perlin then
if planetoids.settings.mode == "perlin" then
init_maps(minp,maxp)
else
planetoids.perlin = true
end
end
--normal block size
local blsize = planetoids.settings.blocksize or {x=5,y=5,z=5}
local halfsize = {x=blsize.x/2,y=blsize.y/2,z=blsize.z/2}
local centre = {x=minp.x+halfsize.x,y=minp.y+halfsize.y,z=minp.z+halfsize.z}
--the size of this block
local blocksize = {x=blsize.x,y=blsize.y,z=blsize.z}
local blockmin = {x=minp.x,y=minp.y,z=minp.z}
local mapsize = {x=maxp.x-minp.x+1,y=maxp.y-minp.y+1,z=maxp.z-minp.z+1}
--bring your own table - reduce garbage collections
local map = byot or {}
local block_byot = nil
if byot then
block_byot = shared_block_byot
end
local source = generate_point_area(minp,maxp,seed)
for z=minp.z,maxp.z,blsize.z do
centre.z = z + halfsize.z
blockmin.z = z
if z + (blsize.z - 1) > maxp.z then
blocksize.z = blsize.z - ((z + (blsize.z - 1)) - maxp.z)
centre.z = z + blocksize.z/2
end
for y=minp.y,maxp.y,blsize.y do
centre.y = y + halfsize.y
blockmin.y = y
if y + (blsize.y - 1) > maxp.y then
blocksize.y = blsize.y - ((y + (blsize.y - 1)) - maxp.y)
centre.y = y + blocksize.y/2
end
for x=minp.x,maxp.x,blsize.x do
centre.x = x + halfsize.x
blockmin.x = x
if x + (blsize.x - 1) > maxp.x then
blocksize.x = blsize.x - ((x + (blsize.x -1)) - maxp.x)
centre.x = x + blocksize.x/2
end
local temp = generate_block(blocksize,centre,blockmin
,seed,source,block_byot)
local blockstart = blockmin.x - minp.x + 1
+ (blockmin.y - minp.y)*mapsize.x
+ (blockmin.z - minp.z)*mapsize.x*mapsize.y
blockfiller(temp,blocksize,map,mapsize,blockstart)
end
end
end
return map
end
planetoids.raw_map = get_planet_map
--This function can be used to scale any compliant 3d map generator
--This adds an extra overhead - but this is negligable
local scale_3d_map_flat = function(minp,maxp,seed,map_gen,byot,scale_byot)
local scale = planetoids.settings.scale
local minp,rmin = minp,minp
local maxp,rmax = maxp,maxp
if scale then
minp = {x=floor(minp.x/scale),y=floor(minp.y/scale)
,z=floor(minp.z/scale)}
--Replace def_table with map object
maxp = {x=floor(maxp.x/scale),y=floor(maxp.y/scale)
,z=floor(maxp.z/scale)}
end
local ret
if scale then
ret = scale_byot or {}
else
ret = byot or {}
end
ret = map_gen(minp,maxp,seed,ret)
if scale then
local nixyz = 1
local scalxyz = 1
local scalsidx = abs(maxp.x - minp.x) + 1
local scalsidy = abs(maxp.y - minp.y) + 1
local sx,sy,sz,ix,iy = 0,0,0,1,1
local table_size = ((rmax.z - rmin.z) + 1)*((rmax.y - rmin.y) + 1)
*((rmax.x - rmin.x) + 1)
local x,y,z = rmin.x,rmin.y,rmin.z
local newret = byot or {}
for z=rmin.z,rmax.z do
sy = 0
for y=rmin.y,rmax.y do
sx = 0
for x=rmin.x,rmax.x do
newret[nixyz] = ret[scalxyz]
nixyz = nixyz + 1
sx = sx + 1
if sx == scale then
scalxyz = scalxyz + 1
sx = 0
end
end
sy = sy + 1
if sy ~= scale then
scalxyz = ix
else
scalxyz = ix + scalsidx
ix = scalxyz
sy = 0
end
end
sz = sz + 1
if sz ~= scale then
scalxyz = iy
ix = iy
else
sz = 0
scalxyz = iy + scalsidy*scalsidx
iy = scalxyz
ix = iy
end
end
ret = newret
end
return ret
end
local shared_scale_byot = {}
--Wrapper to apply scale function if needed
planetoids.get_map_flat = function(minp,maxp,seed,byot)
local scale_byot = nil
if byot then
scale_byot = shared_scale_byot
end
local map_gen = get_planet_map
return scale_3d_map_flat(minp,maxp,seed,map_gen,byot,scale_byot)
end
planetoids.configure = function()
local set = planetoids.settings
--Default seed offset, to avoid errors layer where it is required
set.seed_offset = set.seed_offset or 0
--setup random functions
local sum = 0
local point_dist = set.point_distribution
for i=#point_dist,0,-1 do
if point_dist[i] then
sum = point_dist[i] + sum
end
end
set.point_distribution.rand_max = sum
--setup surface population table
set.pop = {}
for _,node_table in ipairs(set.surface_populator) do
local inner_sum = 0
for _,rep_table in ipairs(node_table) do
--change node names to node ids
rep_table.node = minetest.get_content_id(rep_table.node)
inner_sum = inner_sum + rep_table.rarity
end
node_table.rand_max = inner_sum
node_table.rarity = node_table.population_chance * 10000
set.pop[minetest.get_content_id(node_table.node)] = node_table
end
sum = 0
for i,v in ipairs(set.planet_types) do
local inner_sum = 0
for j,w in ipairs(v) do
--change node names to node ids
if w.crust_material then
w.crust_material = minetest.get_content_id(w.crust_material)
end
if w.crust_top_material then
w.crust_top_material = minetest.get_content_id(w.crust_top_material)
end
if w.filling_material then
w.filling_material = minetest.get_content_id(w.filling_material)
end
inner_sum = inner_sum + w.rarity
end
v.rand_max = inner_sum
sum = sum + v.rarity
end
set.planet_types.rand_max = sum
--setup scale factor
if set.planet_size.sector_scale < 2 then
set.planet_size.sector_scale = 2
end
--setup sector lengths
local length = set.planet_size.maximum * set.planet_size.sector_scale
set.sector_lengths = {x=length,y=length,z=length}
--setup geometry function
set.dist = planetoids.geometry[set.geometry]
set.get_dist = set.dist._3d
--setup layer cache to chache generated points
set.cache = setmetatable({},planetoids.meta_cache)
planetoids.cache = set.cache
end
planetoids.meta_cache = {
__mode = "v",
}
dofile(minetest.get_modpath("planetoids").."/settings.lua")
--MAPGEN
local c_air = minetest.get_content_id("air")
local c_ignore = minetest.get_content_id("ignore")
local c_error = minetest.get_content_id("default:obsidian")
local c_leaves = minetest.get_content_id("default:leaves")
local c_tree = minetest.get_content_id("default:tree")
--Bring your own table for voronoi noise
local planets = {}
local map_seed
minetest.register_on_generated(function(minp, maxp, seed)
local pr = PseudoRandom(seed)
--local time_start = os.clock()
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
local data = vm:get_data()
local side_length = math.abs(maxp.x - minp.x) + 1
local biglen = side_length+32
local chulens = {x=side_length, y=side_length, z=side_length}
if not map_seed then
map_seed = minetest.get_mapgen_params().seed
end
local planets = planets
planets = planetoids.get_map_flat(minp,maxp,map_seed,planets)
local set = planetoids.settings
local nixyz = 1
local nixz = 1
for z = minp.z,maxp.z do
for y = minp.y,maxp.y do
local vi = area:index(minp.x,y,z)
for x = minp.x,maxp.x do
if planets[nixyz] == c_air then
elseif planets[nixyz] ~= c_ignore then
data[vi] = planets[nixyz]
--simple population
local pop = set.pop[planets[nixyz]]
if set.surface_populator.enabled and pop and y < maxp.y
and planets[nixyz+side_length] == c_air
and math.random(1,10000) < pop.rarity then
local num = math.random(1,pop.rand_max)
local cum = 0
for i=#pop,1,-1 do
cum = pop[i].rarity + cum
if num <= cum then
data[area:index(x,y+1,z)] = pop[i].node
break
end
end
end
else
data[vi] = c_error
end
nixyz = nixyz + 1
nixz = nixz + 1
vi = vi + 1
end
nixz = nixz - side_length
end
nixz = nixz + side_length
end
--minetest.debug(os.clock() - time_start)
vm:set_data(data)
vm:set_lighting({day=15, night=0})
vm:calc_lighting()
vm:write_to_map(data)
end)
minetest.register_on_generated(function(minp, maxp, seed)
local pr = PseudoRandom(seed)
local max = planetoids.settings.planet_size.maximum
if minp.x > max or maxp.x < -max or
minp.z > max or maxp.z < -max or
minp.y > 0 or maxp.y < -2 * max then
return
end
local vm, emin, emax = minetest.get_mapgen_object("voxelmanip")
local area = VoxelArea:new{MinEdge=emin, MaxEdge=emax}
local data = vm:get_data()
local side_length = math.abs(maxp.x - minp.x) + 1
if not map_seed then
map_seed = minetest.get_mapgen_params().seed
end
local get_dist = planetoids.settings.get_dist
local centre = {x=0,y=-max,z=0}
local pos = {x=0,y=0,z=0}
local nixyz = 1
local nixz = 1
for z = minp.z,maxp.z do
pos.z = z
for y = minp.y,maxp.y do
pos.y = y
local vi = area:index(minp.x,y,z)
for x = minp.x,maxp.x do
pos.x = x
local dist = get_dist(pos,centre)
if dist < max then
if dist < max - 2 then
data[vi] = c_tree
else
data[vi] = c_leaves
end
end
nixyz = nixyz + 1
nixz = nixz + 1
vi = vi + 1
end
nixz = nixz - side_length
end
nixz = nixz + side_length
end
vm:set_data(data)
vm:set_lighting({day=15, night=0})
vm:calc_lighting()
vm:write_to_map(data)
end)
minetest.register_on_mapgen_init(function(mgparams)
mgparams.mgname = "singlenode"
-- Avoid changing the mapgen seed
mgparams.seed = nil
mgparams.flags = mgparams.flags .. " , nolight "
minetest.set_mapgen_params(mgparams)
end)