--[[ 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