mapgen_geodes.lua 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. --[[
  2. Nether mod for minetest
  3. This file contains helper functions for generating geode interiors,
  4. a proof-of-concept to demonstrate how the secondary/spare region
  5. in the nether might be put to use by someone.
  6. Copyright (C) 2021 Treer
  7. Permission to use, copy, modify, and/or distribute this software for
  8. any purpose with or without fee is hereby granted, provided that the
  9. above copyright notice and this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  11. WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  12. WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
  13. BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
  14. OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
  15. WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
  16. ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  17. SOFTWARE.
  18. ]]--
  19. local debugf = nether.debug
  20. local mapgen = nether.mapgen
  21. -- Content ids
  22. local c_air = minetest.get_content_id("air")
  23. local c_crystal = minetest.get_content_id("nether:geodelite") -- geodelite has a faint glow
  24. local c_netherrack = minetest.get_content_id("nether:rack")
  25. local c_glowstone = minetest.get_content_id("nether:glowstone")
  26. -- Math funcs
  27. 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
  28. -- Create a tiling space of close-packed spheres, using Hexagonal close packing
  29. -- of spheres with radius 0.5.
  30. -- With a layer of spheres on a flat surface, if the pack-z distance is 1 due to 0.5
  31. -- radius then the pack-x distance will be the height of an equilateral triangle: sqrt(3) / 2,
  32. -- and the pack-y distance between each layer will be sqrt(6) / 3,
  33. -- The tessellating space will be a rectangular box of 2*pack-x by 1*pack-z by 3*pack-y
  34. local xPack = math.sqrt(3)/2 -- 0.866, height of an equalateral triangle
  35. local xPack2 = xPack * 2 -- 1.732
  36. local yPack = math.sqrt(6) / 3 -- 0.816, y height of each layer
  37. local yPack2 = yPack * 2
  38. local yPack3 = yPack * 3
  39. local layer2offsetx = xPack / 3 -- 0.289, height to center of equalateral triangle
  40. local layer3offsetx = xPack2 / 3 -- 0.577
  41. local structureSize = 50 -- magic numbers may need retuning if this changes too much
  42. local layer1 = {
  43. {0, 0, 0},
  44. {0, 0, 1},
  45. {xPack, 0, -0.5},
  46. {xPack, 0, 0.5},
  47. {xPack, 0, 1.5},
  48. {xPack2, 0, 0},
  49. {xPack2, 0, 1},
  50. }
  51. local layer2 = {
  52. {layer2offsetx - xPack, yPack, 0},
  53. {layer2offsetx - xPack, yPack, 1},
  54. {layer2offsetx, yPack, -0.5},
  55. {layer2offsetx, yPack, 0.5},
  56. {layer2offsetx, yPack, 1.5},
  57. {layer2offsetx + xPack, yPack, 0},
  58. {layer2offsetx + xPack, yPack, 1},
  59. {layer2offsetx + xPack2, yPack, -0.5},
  60. {layer2offsetx + xPack2, yPack, 0.5},
  61. {layer2offsetx + xPack2, yPack, 1.5},
  62. }
  63. local layer3 = {
  64. {layer3offsetx - xPack, yPack2, -0.5},
  65. {layer3offsetx - xPack, yPack2, 0.5},
  66. {layer3offsetx - xPack, yPack2, 1.5},
  67. {layer3offsetx, yPack2, 0},
  68. {layer3offsetx, yPack2, 1},
  69. {layer3offsetx + xPack, yPack2, -0.5},
  70. {layer3offsetx + xPack, yPack2, 0.5},
  71. {layer3offsetx + xPack, yPack2, 1.5},
  72. {layer3offsetx + xPack2, yPack2, 0},
  73. {layer3offsetx + xPack2, yPack2, 1},
  74. }
  75. local layer4 = {
  76. {0, yPack3, 0},
  77. {0, yPack3, 1},
  78. {xPack, yPack3, -0.5},
  79. {xPack, yPack3, 0.5},
  80. {xPack, yPack3, 1.5},
  81. {xPack2, yPack3, 0},
  82. {xPack2, yPack3, 1},
  83. }
  84. local layers = {
  85. {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
  86. {y = layer2[1][2], points = layer2},
  87. {y = layer3[1][2], points = layer3},
  88. {y = layer4[1][2], points = layer4},
  89. }
  90. -- Geode mapgen functions (AKA proof of secondary/spare region concept)
  91. -- fast for small lists
  92. function insertionSort(array)
  93. local i
  94. for i = 2, #array do
  95. local key = array[i]
  96. local j = i - 1
  97. while j > 0 and array[j] > key do
  98. array[j + 1] = array[j]
  99. j = j - 1
  100. end
  101. array[j + 1] = key
  102. end
  103. return array
  104. end
  105. local distSquaredList = {}
  106. local adj_x = 0
  107. local adj_y = 0
  108. local adj_z = 0
  109. local lasty, lastz
  110. local warpx, warpz
  111. -- It's quite a lot to calculate for each air node, but its not terribly slow and
  112. -- it'll be pretty darn rare for chunks in the secondary region to ever get emerged.
  113. mapgen.getGeodeInteriorNodeId = function(x, y, z)
  114. if z ~= lastz then
  115. lastz = z
  116. -- Calculate structure warping
  117. -- To avoid calculating this for each node there's no warping as you look along the x axis :(
  118. adj_y = math.sin(math_pi / 222 * y) * 30
  119. if y ~= lasty then
  120. lasty = y
  121. warpx = math.sin(math_pi / 100 * y) * 10
  122. warpz = math.sin(math_pi / 43 * y) * 15
  123. end
  124. local twistRadians = math_pi / 73 * y
  125. local sinTwist, cosTwist = math.sin(twistRadians), math.cos(twistRadians)
  126. adj_x = cosTwist * warpx - sinTwist * warpz
  127. adj_z = sinTwist * warpx + cosTwist * warpz
  128. end
  129. -- convert x, y, z into a position in the tessellating space
  130. local cell_x = (((x + adj_x) / xPack2 + 0.5) % structureSize) / structureSize * xPack2
  131. local cell_y = (((y + adj_y) / yPack3 + 0.5) % structureSize) / structureSize * yPack3
  132. local cell_z = (((z + adj_z) + 0.5) % structureSize) / structureSize -- zPack = 1, so can be omitted
  133. local iOut = 1
  134. local i, j
  135. local canSkip = false
  136. for i = 1, #layers do
  137. local layer = layers[i]
  138. local dy = cell_y - layer.y
  139. 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°))
  140. local points = layer.points
  141. for j = 1, #points do
  142. local point = points[j]
  143. local dx = cell_x - point[1]
  144. local dz = cell_z - point[3]
  145. local distSquared = dx*dx + dy*dy + dz*dz
  146. if distSquared < 0.25 then
  147. -- optimization - point is inside a sphere, so cannot be a wall edge. (0.25 comes from radius of 0.5 squared)
  148. return c_air
  149. end
  150. distSquaredList[iOut] = distSquared
  151. iOut = iOut + 1
  152. end
  153. end
  154. end
  155. -- clear the rest of the array instead of creating a new one to hopefully reduce luajit mem leaks.
  156. while distSquaredList[iOut] ~= nil do
  157. rawset(distSquaredList, iOut, nil)
  158. iOut = iOut + 1
  159. end
  160. insertionSort(distSquaredList)
  161. local d3_1 = distSquaredList[3] - distSquaredList[1]
  162. local d3_2 = distSquaredList[3] - distSquaredList[2]
  163. --local d4_1 = distSquaredList[4] - distSquaredList[1]
  164. --local d4_3 = distSquaredList[4] - distSquaredList[3]
  165. -- Some shape formulas (tuned for a structureSize of 50)
  166. -- (d3_1 < 0.05) gives connective lines
  167. -- (d3_1 < 0.05 or d3_2 < .02) give fancy elven bridges - prob doesn't need the d3_1 part
  168. -- ((d3_1 < 0.05 or d3_2 < .02) and distSquaredList[1] > .3) tapers the fancy connections in the middle
  169. -- (d4_3 < 0.03 and d3_2 < 0.03) produces caltrops at intersections
  170. -- (d4_1 < 0.1) produces spherish balls at intersections
  171. -- The idea is voronoi based - edges in a voronoi diagram are where each nearby point is at equal distance.
  172. -- In this case we use squared distances to avoid calculating square roots.
  173. if (d3_1 < 0.05 or d3_2 < .02) and distSquaredList[1] > .3 then
  174. return c_crystal
  175. elseif (distSquaredList[4] - distSquaredList[1]) < 0.08 then
  176. return c_glowstone
  177. else
  178. return c_air
  179. end
  180. end