mirror of
https://github.com/minetest-mods/nether.git
synced 2024-12-27 19:30:28 +01:00
221 lines
7.6 KiB
Lua
221 lines
7.6 KiB
Lua
--[[
|
|
|
|
Nether mod for minetest
|
|
|
|
This file contains helper functions for generating geode interiors,
|
|
a proof-of-concept to demonstrate how the secondary/spare region
|
|
in the nether might be put to use by someone.
|
|
|
|
|
|
Copyright (C) 2021 Treer
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for
|
|
any purpose with or without fee is hereby granted, provided that the
|
|
above copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
|
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
|
|
BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
|
|
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
|
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
|
SOFTWARE.
|
|
|
|
]]--
|
|
|
|
|
|
local debugf = nether.debug
|
|
local mapgen = nether.mapgen
|
|
|
|
|
|
-- Content ids
|
|
|
|
local c_air = minetest.get_content_id("air")
|
|
local c_crystal = minetest.get_content_id("nether:geodelite") -- geodelite has a faint glow
|
|
local c_netherrack = minetest.get_content_id("nether:rack")
|
|
local c_glowstone = minetest.get_content_id("nether:glowstone")
|
|
|
|
-- Math funcs
|
|
local math_max, math_min, math_abs, math_floor, math_pi = math.max, math.min, math.abs, math.floor, math.pi -- avoid needing table lookups each time a common math function is invoked
|
|
|
|
|
|
-- Create a tiling space of close-packed spheres, using Hexagonal close packing
|
|
-- of spheres with radius 0.5.
|
|
-- With a layer of spheres on a flat surface, if the pack-z distance is 1 due to 0.5
|
|
-- radius then the pack-x distance will be the height of an equilateral triangle: sqrt(3) / 2,
|
|
-- and the pack-y distance between each layer will be sqrt(6) / 3,
|
|
-- The tessellating space will be a rectangular box of 2*pack-x by 1*pack-z by 3*pack-y
|
|
|
|
local xPack = math.sqrt(3)/2 -- 0.866, height of an equalateral triangle
|
|
local xPack2 = xPack * 2 -- 1.732
|
|
local yPack = math.sqrt(6) / 3 -- 0.816, y height of each layer
|
|
local yPack2 = yPack * 2
|
|
local yPack3 = yPack * 3
|
|
local layer2offsetx = xPack / 3 -- 0.289, height to center of equalateral triangle
|
|
local layer3offsetx = xPack2 / 3 -- 0.577
|
|
local structureSize = 50 -- magic numbers may need retuning if this changes too much
|
|
|
|
local layer1 = {
|
|
{0, 0, 0},
|
|
{0, 0, 1},
|
|
{xPack, 0, -0.5},
|
|
{xPack, 0, 0.5},
|
|
{xPack, 0, 1.5},
|
|
{xPack2, 0, 0},
|
|
{xPack2, 0, 1},
|
|
}
|
|
local layer2 = {
|
|
{layer2offsetx - xPack, yPack, 0},
|
|
{layer2offsetx - xPack, yPack, 1},
|
|
{layer2offsetx, yPack, -0.5},
|
|
{layer2offsetx, yPack, 0.5},
|
|
{layer2offsetx, yPack, 1.5},
|
|
{layer2offsetx + xPack, yPack, 0},
|
|
{layer2offsetx + xPack, yPack, 1},
|
|
{layer2offsetx + xPack2, yPack, -0.5},
|
|
{layer2offsetx + xPack2, yPack, 0.5},
|
|
{layer2offsetx + xPack2, yPack, 1.5},
|
|
}
|
|
local layer3 = {
|
|
{layer3offsetx - xPack, yPack2, -0.5},
|
|
{layer3offsetx - xPack, yPack2, 0.5},
|
|
{layer3offsetx - xPack, yPack2, 1.5},
|
|
{layer3offsetx, yPack2, 0},
|
|
{layer3offsetx, yPack2, 1},
|
|
{layer3offsetx + xPack, yPack2, -0.5},
|
|
{layer3offsetx + xPack, yPack2, 0.5},
|
|
{layer3offsetx + xPack, yPack2, 1.5},
|
|
{layer3offsetx + xPack2, yPack2, 0},
|
|
{layer3offsetx + xPack2, yPack2, 1},
|
|
}
|
|
local layer4 = {
|
|
{0, yPack3, 0},
|
|
{0, yPack3, 1},
|
|
{xPack, yPack3, -0.5},
|
|
{xPack, yPack3, 0.5},
|
|
{xPack, yPack3, 1.5},
|
|
{xPack2, yPack3, 0},
|
|
{xPack2, yPack3, 1},
|
|
}
|
|
local layers = {
|
|
{y = layer1[1][2], points = layer1}, -- layer1[1][2] is the y value of the first point in layer1, and all spheres in a layer have the same y
|
|
{y = layer2[1][2], points = layer2},
|
|
{y = layer3[1][2], points = layer3},
|
|
{y = layer4[1][2], points = layer4},
|
|
}
|
|
|
|
|
|
-- Geode mapgen functions (AKA proof of secondary/spare region concept)
|
|
|
|
|
|
-- fast for small lists
|
|
function insertionSort(array)
|
|
local i
|
|
for i = 2, #array do
|
|
local key = array[i]
|
|
local j = i - 1
|
|
while j > 0 and array[j] > key do
|
|
array[j + 1] = array[j]
|
|
j = j - 1
|
|
end
|
|
array[j + 1] = key
|
|
end
|
|
return array
|
|
end
|
|
|
|
|
|
local distSquaredList = {}
|
|
local adj_x = 0
|
|
local adj_y = 0
|
|
local adj_z = 0
|
|
local lasty, lastz
|
|
local warpx, warpz
|
|
|
|
|
|
-- It's quite a lot to calculate for each air node, but its not terribly slow and
|
|
-- it'll be pretty darn rare for chunks in the secondary region to ever get emerged.
|
|
mapgen.getGeodeInteriorNodeId = function(x, y, z)
|
|
|
|
if z ~= lastz then
|
|
lastz = z
|
|
-- Calculate structure warping
|
|
-- To avoid calculating this for each node there's no warping as you look along the x axis :(
|
|
adj_y = math.sin(math_pi / 222 * y) * 30
|
|
|
|
if y ~= lasty then
|
|
lasty = y
|
|
warpx = math.sin(math_pi / 100 * y) * 10
|
|
warpz = math.sin(math_pi / 43 * y) * 15
|
|
end
|
|
local twistRadians = math_pi / 73 * y
|
|
local sinTwist, cosTwist = math.sin(twistRadians), math.cos(twistRadians)
|
|
adj_x = cosTwist * warpx - sinTwist * warpz
|
|
adj_z = sinTwist * warpx + cosTwist * warpz
|
|
end
|
|
|
|
-- convert x, y, z into a position in the tessellating space
|
|
local cell_x = (((x + adj_x) / xPack2 + 0.5) % structureSize) / structureSize * xPack2
|
|
local cell_y = (((y + adj_y) / yPack3 + 0.5) % structureSize) / structureSize * yPack3
|
|
local cell_z = (((z + adj_z) + 0.5) % structureSize) / structureSize -- zPack = 1, so can be omitted
|
|
|
|
local iOut = 1
|
|
local i, j
|
|
local canSkip = false
|
|
|
|
for i = 1, #layers do
|
|
|
|
local layer = layers[i]
|
|
local dy = cell_y - layer.y
|
|
|
|
if dy > -0.71 and dy < 0.71 then -- optimization - don't include points to far away to make a difference. (0.71 comes from sin(45°))
|
|
local points = layer.points
|
|
|
|
for j = 1, #points do
|
|
|
|
local point = points[j]
|
|
local dx = cell_x - point[1]
|
|
local dz = cell_z - point[3]
|
|
local distSquared = dx*dx + dy*dy + dz*dz
|
|
|
|
if distSquared < 0.25 then
|
|
-- optimization - point is inside a sphere, so cannot be a wall edge. (0.25 comes from radius of 0.5 squared)
|
|
return c_air
|
|
end
|
|
|
|
distSquaredList[iOut] = distSquared
|
|
iOut = iOut + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
-- clear the rest of the array instead of creating a new one to hopefully reduce luajit mem leaks.
|
|
while distSquaredList[iOut] ~= nil do
|
|
rawset(distSquaredList, iOut, nil)
|
|
iOut = iOut + 1
|
|
end
|
|
|
|
insertionSort(distSquaredList)
|
|
|
|
local d3_1 = distSquaredList[3] - distSquaredList[1]
|
|
local d3_2 = distSquaredList[3] - distSquaredList[2]
|
|
--local d4_1 = distSquaredList[4] - distSquaredList[1]
|
|
--local d4_3 = distSquaredList[4] - distSquaredList[3]
|
|
|
|
-- Some shape formulas (tuned for a structureSize of 50)
|
|
-- (d3_1 < 0.05) gives connective lines
|
|
-- (d3_1 < 0.05 or d3_2 < .02) give fancy elven bridges - prob doesn't need the d3_1 part
|
|
-- ((d3_1 < 0.05 or d3_2 < .02) and distSquaredList[1] > .3) tapers the fancy connections in the middle
|
|
-- (d4_3 < 0.03 and d3_2 < 0.03) produces caltrops at intersections
|
|
-- (d4_1 < 0.1) produces spherish balls at intersections
|
|
-- The idea is voronoi based - edges in a voronoi diagram are where each nearby point is at equal distance.
|
|
-- In this case we use squared distances to avoid calculating square roots.
|
|
|
|
if (d3_1 < 0.05 or d3_2 < .02) and distSquaredList[1] > .3 then
|
|
return c_crystal
|
|
elseif (distSquaredList[4] - distSquaredList[1]) < 0.08 then
|
|
return c_glowstone
|
|
else
|
|
return c_air
|
|
end
|
|
end |