primitives.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. --- Functions for creating primitive shapes.
  2. -- @module worldedit.primitives
  3. local mh = worldedit.manip_helpers
  4. --- Adds a cube
  5. -- @param pos Position of ground level center of cube
  6. -- @param width Cube width. (x)
  7. -- @param height Cube height. (y)
  8. -- @param length Cube length. (z)
  9. -- @param node_name Name of node to make cube of.
  10. -- @param hollow Whether the cube should be hollow.
  11. -- @return The number of nodes added.
  12. function worldedit.cube(pos, width, height, length, node_name, hollow)
  13. -- Set up voxel manipulator
  14. local basepos = vector.subtract(pos, {x=math.floor(width/2), y=0, z=math.floor(length/2)})
  15. local manip, area = mh.init(basepos, vector.add(basepos, {x=width, y=height, z=length}))
  16. local data = mh.get_empty_data(area)
  17. -- Add cube
  18. local node_id = minetest.get_content_id(node_name)
  19. local stride = {x=1, y=area.ystride, z=area.zstride}
  20. local offset = vector.subtract(basepos, area.MinEdge)
  21. local count = 0
  22. for z = 0, length-1 do
  23. local index_z = (offset.z + z) * stride.z + 1 -- +1 for 1-based indexing
  24. for y = 0, height-1 do
  25. local index_y = index_z + (offset.y + y) * stride.y
  26. for x = 0, width-1 do
  27. local is_wall = z == 0 or z == length-1
  28. or y == 0 or y == height-1
  29. or x == 0 or x == width-1
  30. if not hollow or is_wall then
  31. local i = index_y + (offset.x + x)
  32. data[i] = node_id
  33. count = count + 1
  34. end
  35. end
  36. end
  37. end
  38. mh.finish(manip, data)
  39. return count
  40. end
  41. --- Adds a sphere of `node_name` centered at `pos`.
  42. -- @param pos Position to center sphere at.
  43. -- @param radius Sphere radius.
  44. -- @param node_name Name of node to make shere of.
  45. -- @param hollow Whether the sphere should be hollow.
  46. -- @return The number of nodes added.
  47. function worldedit.sphere(pos, radius, node_name, hollow)
  48. local manip, area = mh.init_radius(pos, radius)
  49. local data = mh.get_empty_data(area)
  50. -- Fill selected area with node
  51. local node_id = minetest.get_content_id(node_name)
  52. local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
  53. local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
  54. local stride_z, stride_y = area.zstride, area.ystride
  55. local count = 0
  56. for z = -radius, radius do
  57. -- Offset contributed by z plus 1 to make it 1-indexed
  58. local new_z = (z + offset_z) * stride_z + 1
  59. for y = -radius, radius do
  60. local new_y = new_z + (y + offset_y) * stride_y
  61. for x = -radius, radius do
  62. local squared = x * x + y * y + z * z
  63. if squared <= max_radius and (not hollow or squared >= min_radius) then
  64. -- Position is on surface of sphere
  65. local i = new_y + (x + offset_x)
  66. data[i] = node_id
  67. count = count + 1
  68. end
  69. end
  70. end
  71. end
  72. mh.finish(manip, data)
  73. return count
  74. end
  75. --- Adds a dome.
  76. -- @param pos Position to center dome at.
  77. -- @param radius Dome radius. Negative for concave domes.
  78. -- @param node_name Name of node to make dome of.
  79. -- @param hollow Whether the dome should be hollow.
  80. -- @return The number of nodes added.
  81. -- TODO: Add axis option.
  82. function worldedit.dome(pos, radius, node_name, hollow)
  83. local min_y, max_y = 0, radius
  84. if radius < 0 then
  85. radius = -radius
  86. min_y, max_y = -radius, 0
  87. end
  88. local manip, area = mh.init_axis_radius(pos, "y", radius)
  89. local data = mh.get_empty_data(area)
  90. -- Add dome
  91. local node_id = minetest.get_content_id(node_name)
  92. local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
  93. local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
  94. local stride_z, stride_y = area.zstride, area.ystride
  95. local count = 0
  96. for z = -radius, radius do
  97. local new_z = (z + offset_z) * stride_z + 1 --offset contributed by z plus 1 to make it 1-indexed
  98. for y = min_y, max_y do
  99. local new_y = new_z + (y + offset_y) * stride_y
  100. for x = -radius, radius do
  101. local squared = x * x + y * y + z * z
  102. if squared <= max_radius and (not hollow or squared >= min_radius) then
  103. -- Position is in dome
  104. local i = new_y + (x + offset_x)
  105. data[i] = node_id
  106. count = count + 1
  107. end
  108. end
  109. end
  110. end
  111. mh.finish(manip, data)
  112. return count
  113. end
  114. --- Adds a cylinder.
  115. -- @param pos Position to center base of cylinder at.
  116. -- @param axis Axis ("x", "y", or "z")
  117. -- @param length Cylinder length.
  118. -- @param radius1 Cylinder base radius.
  119. -- @param radius2 Cylinder top radius.
  120. -- @param node_name Name of node to make cylinder of.
  121. -- @param hollow Whether the cylinder should be hollow.
  122. -- @return The number of nodes added.
  123. function worldedit.cylinder(pos, axis, length, radius1, radius2, node_name, hollow)
  124. local other1, other2 = worldedit.get_axis_others(axis)
  125. -- Backwards compatibility
  126. if type(radius2) == "string" then
  127. hollow = node_name
  128. node_name = radius2
  129. radius2 = radius1 -- straight cylinder
  130. end
  131. -- Handle negative lengths
  132. local current_pos = {x=pos.x, y=pos.y, z=pos.z}
  133. if length < 0 then
  134. length = -length
  135. current_pos[axis] = current_pos[axis] - length
  136. radius1, radius2 = radius2, radius1
  137. end
  138. -- Set up voxel manipulator
  139. local manip, area = mh.init_axis_radius_length(current_pos, axis, math.max(radius1, radius2), length)
  140. local data = mh.get_empty_data(area)
  141. -- Add desired shape (anything inbetween cylinder & cone)
  142. local node_id = minetest.get_content_id(node_name)
  143. local stride = {x=1, y=area.ystride, z=area.zstride}
  144. local offset = {
  145. x = current_pos.x - area.MinEdge.x,
  146. y = current_pos.y - area.MinEdge.y,
  147. z = current_pos.z - area.MinEdge.z,
  148. }
  149. local count = 0
  150. for i = 0, length - 1 do
  151. -- Calulate radius for this "height" in the cylinder
  152. local radius = radius1 + (radius2 - radius1) * (i + 1) / length
  153. radius = math.floor(radius + 0.5) -- round
  154. local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
  155. for index2 = -radius, radius do
  156. -- Offset contributed by other axis 1 plus 1 to make it 1-indexed
  157. local new_index2 = (index2 + offset[other1]) * stride[other1] + 1
  158. for index3 = -radius, radius do
  159. local new_index3 = new_index2 + (index3 + offset[other2]) * stride[other2]
  160. local squared = index2 * index2 + index3 * index3
  161. if squared <= max_radius and (not hollow or squared >= min_radius) then
  162. -- Position is in cylinder, add node here
  163. local vi = new_index3 + (offset[axis] + i) * stride[axis]
  164. data[vi] = node_id
  165. count = count + 1
  166. end
  167. end
  168. end
  169. end
  170. mh.finish(manip, data)
  171. return count
  172. end
  173. --- Adds a pyramid.
  174. -- @param pos Position to center base of pyramid at.
  175. -- @param axis Axis ("x", "y", or "z")
  176. -- @param height Pyramid height.
  177. -- @param node_name Name of node to make pyramid of.
  178. -- @param hollow Whether the pyramid should be hollow.
  179. -- @return The number of nodes added.
  180. function worldedit.pyramid(pos, axis, height, node_name, hollow)
  181. local other1, other2 = worldedit.get_axis_others(axis)
  182. -- Set up voxel manipulator
  183. -- FIXME: passing negative <radius> causes mis-sorted pos to be passed
  184. -- into mh.init() which is technically not allowed but works
  185. local manip, area = mh.init_axis_radius(pos, axis, height)
  186. local data = mh.get_empty_data(area)
  187. -- Handle inverted pyramids
  188. local step
  189. if height > 0 then
  190. height = height - 1
  191. step = 1
  192. else
  193. height = height + 1
  194. step = -1
  195. end
  196. -- Add pyramid
  197. local node_id = minetest.get_content_id(node_name)
  198. local stride = {x=1, y=area.ystride, z=area.zstride}
  199. local offset = {
  200. x = pos.x - area.MinEdge.x,
  201. y = pos.y - area.MinEdge.y,
  202. z = pos.z - area.MinEdge.z,
  203. }
  204. local size = math.abs(height * step)
  205. local count = 0
  206. -- For each level of the pyramid
  207. for index1 = 0, height, step do
  208. -- Offset contributed by axis plus 1 to make it 1-indexed
  209. local new_index1 = (index1 + offset[axis]) * stride[axis] + 1
  210. for index2 = -size, size do
  211. local new_index2 = new_index1 + (index2 + offset[other1]) * stride[other1]
  212. for index3 = -size, size do
  213. local i = new_index2 + (index3 + offset[other2]) * stride[other2]
  214. if (not hollow or size - math.abs(index2) < 2 or size - math.abs(index3) < 2) then
  215. data[i] = node_id
  216. count = count + 1
  217. end
  218. end
  219. end
  220. size = size - 1
  221. end
  222. mh.finish(manip, data)
  223. return count
  224. end
  225. --- Adds a spiral.
  226. -- @param pos Position to center spiral at.
  227. -- @param length Spral length.
  228. -- @param height Spiral height.
  229. -- @param spacer Space between walls.
  230. -- @param node_name Name of node to make spiral of.
  231. -- @return Number of nodes added.
  232. -- TODO: Add axis option.
  233. function worldedit.spiral(pos, length, height, spacer, node_name)
  234. local extent = math.ceil(length / 2)
  235. local manip, area = mh.init_axis_radius_length(pos, "y", extent, height)
  236. local data = mh.get_empty_data(area)
  237. -- Set up variables
  238. local node_id = minetest.get_content_id(node_name)
  239. local stride = {x=1, y=area.ystride, z=area.zstride}
  240. local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
  241. local i = offset_z * stride.z + offset_y * stride.y + offset_x + 1
  242. -- Add first column
  243. local count = height
  244. local column = i
  245. for y = 1, height do
  246. data[column] = node_id
  247. column = column + stride.y
  248. end
  249. -- Add spiral segments
  250. local stride_axis, stride_other = stride.x, stride.z
  251. local sign = -1
  252. local segment_length = 0
  253. spacer = spacer + 1
  254. -- Go through each segment except the last
  255. for segment = 1, math.floor(length / spacer) * 2 do
  256. -- Change sign and length every other turn starting with the first
  257. if segment % 2 == 1 then
  258. sign = -sign
  259. segment_length = segment_length + spacer
  260. end
  261. -- Fill segment
  262. for index = 1, segment_length do
  263. -- Move along the direction of the segment
  264. i = i + stride_axis * sign
  265. local column = i
  266. -- Add column
  267. for y = 1, height do
  268. data[column] = node_id
  269. column = column + stride.y
  270. end
  271. end
  272. count = count + segment_length * height
  273. stride_axis, stride_other = stride_other, stride_axis -- Swap axes
  274. end
  275. -- Add shorter final segment
  276. sign = -sign
  277. for index = 1, segment_length do
  278. i = i + stride_axis * sign
  279. local column = i
  280. -- Add column
  281. for y = 1, height do
  282. data[column] = node_id
  283. column = column + stride.y
  284. end
  285. end
  286. count = count + segment_length * height
  287. mh.finish(manip, data)
  288. return count
  289. end