A minetest mod for mapmaking
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

269 lines
7.6KB

  1. -- Arguments
  2. -- map_data: The cartographer map data table
  3. -- chunk: The chunk coordinate conversion API
  4. -- settings: The mod settings
  5. local map_data, chunk, settings = ...;
  6. local scan_queue = {};
  7. -- Register a new tile in map data
  8. -- x: The x position in map coordinates
  9. -- y: The y position in map coordinates
  10. -- biome: The tile's biome id
  11. -- height: The tile's height
  12. -- (Optional): manual_scan: Indicates if this was a 'manual' (non-generated)
  13. -- scan. Manual scans are overridden by generated
  14. -- scans under normal circumstances.
  15. local function register_tile(x, y, biome, height, manual_scan)
  16. if not map_data.generated[x] then
  17. map_data.generated[x] = {
  18. [y] = {
  19. biome = biome,
  20. height = height,
  21. }
  22. };
  23. if manual_scan ~= nil then
  24. map_data.generated[x][y].manual_scan = manual_scan;
  25. end
  26. elseif not map_data.generated[x][y] or map_data.generated[x][y].height < height then
  27. map_data.generated[x][y] = {
  28. biome = biome,
  29. height = height,
  30. };
  31. if manual_scan ~= nil or map_data.generated[x][y].manual_scan then
  32. map_data.generated[x][y].manual_scan = manual_scan;
  33. end
  34. end
  35. end
  36. -- Get the biome and height data for a region from mapgen data
  37. --
  38. -- min: The min coord of the generated terrain
  39. -- max: The max coord of the generated terrain
  40. -- mmin: The min coord of the region to scan
  41. -- mmax: The max coord of the region to scan
  42. --
  43. -- Returns the biome and height of the scanned region
  44. local function get_mapgen_biome(min, max, mmin, mmax)
  45. local UNDERGROUND = minetest.get_biome_id("underground");
  46. local DEFAULT = minetest.get_biome_id("default");
  47. local biomes = minetest.get_mapgen_object("biomemap");
  48. local heights = minetest.get_mapgen_object("heightmap");
  49. local xx = max.x - min.x;
  50. local zz = max.z - min.z;
  51. local xxx = mmax.x - mmin.x;
  52. local startx = min.x - mmin.x;
  53. local startz = min.z - mmin.z;
  54. local scan_biomes = {};
  55. local scan_heights = {};
  56. for i = startx,startx + xx,1 do
  57. for k = startz,startz + zz,1 do
  58. local b = biomes[i + (k * (xxx + 1))];
  59. if b ~= nil and b ~= UNDERGROUND and b ~= DEFAULT then
  60. scan_biomes[b] = (scan_biomes[b] or 0) + 1;
  61. scan_heights[b] = (scan_heights[b] or 0) + heights[i + (k * (xxx + 1))];
  62. end
  63. end
  64. end
  65. local biome = nil;
  66. local high = 0;
  67. for k,v in pairs(scan_biomes) do
  68. if v > high then
  69. biome = k;
  70. high = v;
  71. end
  72. end
  73. local avg_height = 0;
  74. if high > 0 then
  75. avg_height = scan_heights[biome] / high;
  76. end
  77. return biome, avg_height;
  78. end
  79. -- Get the biome and height data for a region from existing terrain
  80. --
  81. -- min: The min coord of the region to scan
  82. -- max: The max coord of the region to scan
  83. --
  84. -- Returns the biome and height of the scanned region
  85. local function get_biome(min, max)
  86. local UNDERGROUND = minetest.get_biome_id("underground");
  87. local DEFAULT = minetest.get_biome_id("default");
  88. local WATER_SOURCE = minetest.registered_aliases["mapgen_water_source"];
  89. local scan_biomes = {};
  90. local scan_heights = {};
  91. for i = min.x,max.x,1 do
  92. for j = min.y,max.y,1 do
  93. for k = min.z,max.z,1 do
  94. local pos = { x=i, y=j, z=k };
  95. local b = minetest.get_biome_data(pos).biome;
  96. local node = minetest.get_node(pos).name;
  97. if b ~= nil and b ~= UNDERGROUND and b ~= DEFAULT and node ~= "air" and node ~= WATER_SOURCE then
  98. pos.y = pos.y + 1;
  99. node = minetest.get_node(pos).name;
  100. if node == "air" or node == WATER_SOURCE then
  101. scan_biomes[b] = (scan_biomes[b] or 0) + 1;
  102. scan_heights[b] = (scan_heights[b] or 0) + j;
  103. end
  104. end
  105. end
  106. end
  107. end
  108. local biome = nil;
  109. local high = 0;
  110. for k,v in pairs(scan_biomes) do
  111. if v > high then
  112. biome = k;
  113. high = v;
  114. end
  115. end
  116. local avg_height = 0;
  117. if high > 0 then
  118. avg_height = scan_heights[biome] / high;
  119. end
  120. return biome, avg_height;
  121. end
  122. -- Called when new terrain is generated
  123. --
  124. -- min: The min coord of the generated terrain
  125. -- max: The max coord of the generated terrain
  126. local function on_generated(min, max, _)
  127. for i = chunk.to(min.x),chunk.to(max.x),1 do
  128. for j = chunk.to(min.z),chunk.to(max.z),1 do
  129. local sub_min = {
  130. x = chunk.from(i),
  131. y = min.y,
  132. z = chunk.from(j),
  133. };
  134. local sub_max = {
  135. x = chunk.from(i + 1),
  136. y = max.y,
  137. z = chunk.from(j + 1),
  138. };
  139. local biome, height = get_mapgen_biome(sub_min, sub_max, min, max);
  140. if biome ~= nil then
  141. register_tile(i, j, biome, height)
  142. end
  143. end
  144. end
  145. end
  146. minetest.register_on_generated(on_generated);
  147. -- Is the scan of this position already handled?
  148. --
  149. -- x: The x position, in map coordinates
  150. -- y: The y position, in world coordinates
  151. -- x: The z position, in map coordinates
  152. --
  153. -- Returns true if the position is handled by the current map data
  154. local function is_scan_handled(x, y, z)
  155. if not map_data.generated[x] then
  156. return false;
  157. end
  158. local tile = map_data.generated[x][z];
  159. return tile and ((not tile.manual_scan and tile.height > 0) or tile.height >= y);
  160. end
  161. local scanner = {};
  162. -- Queue a tile for manual scanning
  163. --
  164. -- pos: The position as a table, in world coordinates
  165. function scanner.queue_region(pos)
  166. local converted = {
  167. x = chunk.from(chunk.to(pos.x)),
  168. y = chunk.from(chunk.to(pos.y)),
  169. z = chunk.from(chunk.to(pos.z)),
  170. };
  171. if is_scan_handled(chunk.to(pos.x), pos.y, chunk.to(pos.z)) then
  172. return;
  173. end
  174. for _,queued_pos in ipairs(scan_queue) do
  175. if vector.equals(converted, queued_pos) then
  176. return;
  177. end
  178. end
  179. scan_queue[#scan_queue + 1] = converted;
  180. end
  181. local function scan_internal()
  182. local startpos = scan_queue[1];
  183. local chunk_x = chunk.to(startpos.x);
  184. local chunk_y = chunk.to(startpos.y);
  185. local chunk_z = chunk.to(startpos.z);
  186. local endpos = {
  187. x = chunk.from(chunk_x + 1),
  188. y = chunk.from(chunk_y + 1),
  189. z = chunk.from(chunk_z + 1),
  190. };
  191. if is_scan_handled(chunk_x, startpos.y, chunk_z) then
  192. table.remove(scan_queue, 1);
  193. return;
  194. end
  195. local biome,height = get_biome(startpos, endpos);
  196. if biome ~= nil then
  197. register_tile(chunk_x, chunk_z, biome, height, true)
  198. end
  199. table.remove(scan_queue, 1);
  200. end
  201. -- Scan the next N tiles on the queue, and remove them
  202. -- N is determined by backup_scan_count
  203. --
  204. -- flush: Flush the entire scan queue, scanning all queued regions
  205. function scanner.scan_regions(flush)
  206. if #scan_queue == 0 then
  207. return;
  208. end
  209. if settings.backup_scan_count == 0 or flush then
  210. while #scan_queue > 0 do
  211. scan_internal();
  212. end
  213. else
  214. for _=1,settings.backup_scan_count do
  215. if #scan_queue == 0 then
  216. break;
  217. end
  218. scan_internal();
  219. end
  220. end
  221. end
  222. if settings.backup_scan_freq ~= 0 then
  223. local function periodic_scan()
  224. scanner.scan_regions();
  225. minetest.after(settings.backup_scan_freq, periodic_scan);
  226. end
  227. minetest.after(settings.backup_scan_freq, periodic_scan);
  228. end
  229. return scanner;