]> git.lizzy.rs Git - worldedit.git/blob - worldedit/primitives.lua
More speedups for //hollowsphere, //sphere, //hollowdome, //dome, //cylinder, better...
[worldedit.git] / worldedit / primitives.lua
1 worldedit = worldedit or {}\r
2 local minetest = minetest --local copy of global\r
3 \r
4 --adds a hollow sphere centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added\r
5 worldedit.hollow_sphere = function(pos, radius, nodename)\r
6         --set up voxel manipulator\r
7         local manip = minetest.get_voxel_manip()\r
8         local pos1 = {x=pos.x - radius, y=pos.y - radius, z=pos.z - radius}\r
9         local pos2 = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius}\r
10         local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
11         local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
12 \r
13         --fill emerged area with ignore\r
14         local nodes = {}\r
15         local ignore = minetest.get_content_id("ignore")\r
16         for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do\r
17                 nodes[i] = ignore\r
18         end\r
19 \r
20         --fill selected area with node\r
21         local node_id = minetest.get_content_id(nodename)\r
22         local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)\r
23         local offsetx, offsety, offsetz = pos.x - emerged_pos1.x, pos.y - emerged_pos1.y, pos.z - emerged_pos1.z\r
24         local zstride, ystride = area.zstride, area.ystride\r
25         local count = 0\r
26         for z = -radius, radius do\r
27                 local newz = (z + offsetz) * zstride\r
28                 for y = -radius, radius do\r
29                         local newy = newz + (y + offsety) * ystride\r
30                         for x = -radius, radius do\r
31                                 local squared = x * x + y * y + z * z\r
32                                 if squared >= min_radius and squared <= max_radius then --position is on surface of sphere\r
33                                         local i = newy + (x + offsetx) + 1\r
34                                         nodes[i] = node_id\r
35                                         count = count + 1\r
36                                 end\r
37                         end\r
38                 end\r
39         end\r
40 \r
41         --update map nodes\r
42         manip:set_data(nodes)\r
43         manip:write_to_map()\r
44         manip:update_map()\r
45 \r
46         return count\r
47 end\r
48 \r
49 --adds a sphere centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added\r
50 worldedit.sphere = function(pos, radius, nodename)\r
51         --set up voxel manipulator\r
52         local manip = minetest.get_voxel_manip()\r
53         local pos1 = {x=pos.x - radius, y=pos.y - radius, z=pos.z - radius}\r
54         local pos2 = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius}\r
55         local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
56         local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
57 \r
58         --fill emerged area with ignore\r
59         local nodes = {}\r
60         local ignore = minetest.get_content_id("ignore")\r
61         for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do\r
62                 nodes[i] = ignore\r
63         end\r
64 \r
65         --fill selected area with node\r
66         local node_id = minetest.get_content_id(nodename)\r
67         local max_radius = radius * (radius + 1)\r
68         local offsetx, offsety, offsetz = pos.x - emerged_pos1.x, pos.y - emerged_pos1.y, pos.z - emerged_pos1.z\r
69         local zstride, ystride = area.zstride, area.ystride\r
70         local count = 0\r
71         for z = -radius, radius do\r
72                 local newz = (z + offsetz) * zstride\r
73                 for y = -radius, radius do\r
74                         local newy = newz + (y + offsety) * ystride\r
75                         for x = -radius, radius do\r
76                                 if x * x + y * y + z * z <= max_radius then --position is inside sphere\r
77                                         local i = newy + (x + offsetx) + 1\r
78                                         nodes[i] = node_id\r
79                                         count = count + 1\r
80                                 end\r
81                         end\r
82                 end\r
83         end\r
84 \r
85         --update map nodes\r
86         manip:set_data(nodes)\r
87         manip:write_to_map()\r
88         manip:update_map()\r
89 \r
90         return count\r
91 end\r
92 \r
93 --adds a hollow dome centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added\r
94 worldedit.hollow_dome = function(pos, radius, nodename)\r
95         --set up voxel manipulator\r
96         local manip = minetest.get_voxel_manip()\r
97         local pos1 = {x=pos.x - radius, y=pos.y, z=pos.z - radius}\r
98         local pos2 = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius}\r
99         local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
100         local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
101 \r
102         --fill emerged area with ignore\r
103         local nodes = {}\r
104         local ignore = minetest.get_content_id("ignore")\r
105         for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do\r
106                 nodes[i] = ignore\r
107         end\r
108 \r
109         --fill selected area with node\r
110         local node_id = minetest.get_content_id(nodename)\r
111         local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)\r
112         local offsetx, offsety, offsetz = pos.x - emerged_pos1.x, pos.y - emerged_pos1.y, pos.z - emerged_pos1.z\r
113         local zstride, ystride = area.zstride, area.ystride\r
114         local count = 0\r
115         for z = -radius, radius do\r
116                 local newz = (z + offsetz) * zstride\r
117                 for y = 0, radius do\r
118                         local newy = newz + (y + offsety) * ystride\r
119                         for x = -radius, radius do\r
120                                 local squared = x * x + y * y + z * z\r
121                                 if squared >= min_radius and squared <= max_radius then --position is on surface of sphere\r
122                                         local i = newy + (x + offsetx) + 1\r
123                                         nodes[i] = node_id\r
124                                         count = count + 1\r
125                                 end\r
126                         end\r
127                 end\r
128         end\r
129 \r
130         --update map nodes\r
131         manip:set_data(nodes)\r
132         manip:write_to_map()\r
133         manip:update_map()\r
134 \r
135         return count\r
136 end\r
137 \r
138 --adds a dome centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added\r
139 worldedit.dome = function(pos, radius, nodename) --wip: use bresenham sphere for maximum speed\r
140         --set up voxel manipulator\r
141         local manip = minetest.get_voxel_manip()\r
142         local pos1 = {x=pos.x - radius, y=pos.y, z=pos.z - radius}\r
143         local pos2 = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius}\r
144         local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
145         local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
146 \r
147         --fill emerged area with ignore\r
148         local nodes = {}\r
149         local ignore = minetest.get_content_id("ignore")\r
150         for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do\r
151                 nodes[i] = ignore\r
152         end\r
153 \r
154         --fill selected area with node\r
155         local node_id = minetest.get_content_id(nodename)\r
156         local max_radius = radius * (radius + 1)\r
157         local offsetx, offsety, offsetz = pos.x - emerged_pos1.x, pos.y - emerged_pos1.y, pos.z - emerged_pos1.z\r
158         local zstride, ystride = area.zstride, area.ystride\r
159         local count = 0\r
160         for z = -radius, radius do\r
161                 local newz = (z + offsetz) * zstride\r
162                 for y = 0, radius do\r
163                         local newy = newz + (y + offsety) * ystride\r
164                         for x = -radius, radius do\r
165                                 if x * x + y * y + z * z <= max_radius then --position is inside sphere\r
166                                         local i = newy + (x + offsetx) + 1\r
167                                         nodes[i] = node_id\r
168                                         count = count + 1\r
169                                 end\r
170                         end\r
171                 end\r
172         end\r
173 \r
174         --update map nodes\r
175         manip:set_data(nodes)\r
176         manip:write_to_map()\r
177         manip:update_map()\r
178 \r
179         return count\r
180 end\r
181 \r
182 --adds a hollow cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`, returning the number of nodes added\r
183 worldedit.hollow_cylinder = function(pos, axis, length, radius, nodename) --wip: rewrite this using voxelmanip\r
184         local other1, other2\r
185         if axis == "x" then\r
186                 other1, other2 = "y", "z"\r
187         elseif axis == "y" then\r
188                 other1, other2 = "x", "z"\r
189         else --axis == "z"\r
190                 other1, other2 = "x", "y"\r
191         end\r
192 \r
193         --handle negative lengths\r
194         local currentpos = {x=pos.x, y=pos.y, z=pos.z}\r
195         if length < 0 then\r
196                 length = -length\r
197                 currentpos[axis] = currentpos[axis] - length\r
198         end\r
199 \r
200         --create schematic for single node column along the axis\r
201         local node = {name=nodename, param1=0, param2=0}\r
202         local nodes = {}\r
203         for i = 1, length do\r
204                 nodes[i] = node\r
205         end\r
206         local schematic = {size={[axis]=length, [other1]=1, [other2]=1}, data=nodes}\r
207 \r
208         --add columns in a circle around axis to form cylinder\r
209         local place_schematic = minetest.place_schematic\r
210         local count = 0\r
211         local offset1, offset2 = 0, radius\r
212         local delta = -radius\r
213         while offset1 <= offset2 do\r
214                 --add node at each octant\r
215                 local first1, first2 = pos[other1] + offset1, pos[other1] - offset1\r
216                 local second1, second2 = pos[other2] + offset2, pos[other2] - offset2\r
217                 currentpos[other1], currentpos[other2] = first1, second1\r
218                 place_schematic(currentpos, schematic) --octant 1\r
219                 currentpos[other1] = first2\r
220                 place_schematic(currentpos, schematic) --octant 4\r
221                 currentpos[other2] = second2\r
222                 place_schematic(currentpos, schematic) --octant 5\r
223                 currentpos[other1] = first1\r
224                 place_schematic(currentpos, schematic) --octant 8\r
225                 local first1, first2 = pos[other1] + offset2, pos[other1] - offset2\r
226                 local second1, second2 = pos[other2] + offset1, pos[other2] - offset1\r
227                 currentpos[other1], currentpos[other2] = first1, second1\r
228                 place_schematic(currentpos, schematic) --octant 2\r
229                 currentpos[other1] = first2\r
230                 place_schematic(currentpos, schematic) --octant 3\r
231                 currentpos[other2] = second2\r
232                 place_schematic(currentpos, schematic) --octant 6\r
233                 currentpos[other1] = first1\r
234                 place_schematic(currentpos, schematic) --octant 7\r
235 \r
236                 count = count + 8 --wip: broken because sometimes currentpos is repeated\r
237 \r
238                 --move to next location\r
239                 delta = delta + (offset1 * 2) + 1\r
240                 if delta >= 0 then\r
241                         offset2 = offset2 - 1\r
242                         delta = delta - (offset2 * 2)\r
243                 end\r
244                 offset1 = offset1 + 1\r
245         end\r
246         count = count * length --apply the length to the number of nodes\r
247         return count\r
248 end\r
249 \r
250 --adds a cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`, returning the number of nodes added\r
251 worldedit.cylinder = function(pos, axis, length, radius, nodename, env) --wip: rewrite this using voxelmanip\r
252         local other1, other2\r
253         if axis == "x" then\r
254                 other1, other2 = "y", "z"\r
255         elseif axis == "y" then\r
256                 other1, other2 = "x", "z"\r
257         else --axis == "z"\r
258                 other1, other2 = "x", "y"\r
259         end\r
260 \r
261         --handle negative lengths\r
262         local currentpos = {x=pos.x, y=pos.y, z=pos.z}\r
263         if length < 0 then\r
264                 length = -length\r
265                 currentpos[axis] = currentpos[axis] - length\r
266         end\r
267 \r
268         --set up voxel manipulator\r
269         local manip = minetest.get_voxel_manip()\r
270         local pos1 = {\r
271                 [axis]=currentpos[axis],\r
272                 [other1]=currentpos[other1] - radius,\r
273                 [other2]=currentpos[other2] - radius\r
274         }\r
275         local pos2 = {\r
276                 [axis]=currentpos[axis] + length - 1,\r
277                 [other1]=currentpos[other1] + radius,\r
278                 [other2]=currentpos[other2] + radius\r
279         }\r
280         local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
281         local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
282 \r
283         --fill emerged area with ignore\r
284         local nodes = {}\r
285         local ignore = minetest.get_content_id("ignore")\r
286         for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do\r
287                 nodes[i] = ignore\r
288         end\r
289 \r
290         --fill selected area with node\r
291         local node_id = minetest.get_content_id(nodename)\r
292         local max_radius = radius * (radius + 1)\r
293         local stride = {x=1, y=area.ystride, z=area.zstride}\r
294         local offset = {x=currentpos.x - emerged_pos1.x, y=currentpos.y - emerged_pos1.y, z=currentpos.z - emerged_pos1.z}\r
295         local min_slice, max_slice = offset[axis], offset[axis] + length - 1\r
296         local count = 0\r
297         for axis1 = -radius, radius do\r
298                 local newaxis1 = (axis1 + offset[other1]) * stride[other1]\r
299                 for axis2 = -radius, radius do\r
300                         local newaxis2 = newaxis1 + (axis2 + offset[other2]) * stride[other2]\r
301                         if axis1 * axis1 + axis2 * axis2 <= max_radius then\r
302                                 for slice = min_slice, max_slice do\r
303                                         local i = newaxis2 + slice * stride[axis] + 1\r
304                                         nodes[i] = node_id\r
305                                 end\r
306                                 count = count + length\r
307                         end\r
308                 end\r
309         end\r
310 \r
311         --update map nodes\r
312         manip:set_data(nodes)\r
313         manip:write_to_map()\r
314         manip:update_map()\r
315 \r
316         return count\r
317 end\r
318 \r
319 --adds a pyramid centered at `pos` with height `height`, composed of `nodename`, returning the number of nodes added\r
320 worldedit.pyramid = function(pos, height, nodename, env) --wip: rewrite this using voxelmanip\r
321         local pos1x, pos1y, pos1z = pos.x - height, pos.y, pos.z - height\r
322         local pos2x, pos2y, pos2z = pos.x + height, pos.y + height, pos.z + height\r
323         local pos = {x=0, y=pos1y, z=0}\r
324 \r
325         local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
326 \r
327         --set up voxel manipulator\r
328         local manip = minetest.get_voxel_manip()\r
329         local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)\r
330         local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})\r
331 \r
332         --fill emerged area with ignore\r
333         local nodes = {}\r
334         local ignore = minetest.get_content_id("ignore")\r
335         for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do\r
336                 nodes[i] = ignore\r
337         end\r
338 \r
339         --fill selected area with node\r
340         local node_id = minetest.get_content_id(nodename)\r
341         for i in area:iterp(pos1, pos2) do\r
342                 nodes[i] = node_id\r
343         end\r
344 \r
345         --update map nodes\r
346         manip:set_data(nodes)\r
347         manip:write_to_map()\r
348         manip:update_map()\r
349 \r
350         local count = 0\r
351         local node = {name=nodename}\r
352         if env == nil then env = minetest.env end\r
353         while pos.y <= pos2y do --each vertical level of the pyramid\r
354                 pos.x = pos1x\r
355                 while pos.x <= pos2x do\r
356                         pos.z = pos1z\r
357                         while pos.z <= pos2z do\r
358                                 env:add_node(pos, node)\r
359                                 pos.z = pos.z + 1\r
360                         end\r
361                         pos.x = pos.x + 1\r
362                 end\r
363                 count = count + ((pos2y - pos.y) * 2 + 1) ^ 2\r
364                 pos.y = pos.y + 1\r
365 \r
366                 pos1x, pos2x = pos1x + 1, pos2x - 1\r
367                 pos1z, pos2z = pos1z + 1, pos2z - 1\r
368 \r
369         end\r
370         return count\r
371 end\r
372 \r
373 --adds a spiral centered at `pos` with width `width`, height `height`, space between walls `spacer`, composed of `nodename`, returning the number of nodes added\r
374 worldedit.spiral = function(pos, width, height, spacer, nodename, env) --wip: clean this up\r
375         -- spiral matrix - http://rosettacode.org/wiki/Spiral_matrix#Lua\r
376         --wip: rewrite this whole thing, nobody can understand it anyways\r
377         av, sn = math.abs, function(s) return s~=0 and s/av(s) or 0 end\r
378         local function sindex(z, x) -- returns the value at (x, z) in a spiral that starts at 1 and goes outwards\r
379                 if z == -x and z >= x then return (2*z+1)^2 end\r
380                 local l = math.max(av(z), av(x))\r
381                 return (2*l-1)^2+4*l+2*l*sn(x+z)+sn(z^2-x^2)*(l-(av(z)==l and sn(z)*x or sn(x)*z)) -- OH GOD WHAT\r
382         end\r
383         local function spiralt(side)\r
384                 local ret, id, start, stop = {}, 0, math.floor((-side+1)/2), math.floor((side-1)/2)\r
385                 for i = 1, side do\r
386                         for j = 1, side do\r
387                                 local id = side^2 - sindex(stop - i + 1,start + j - 1)\r
388                                 ret[id] = {x=i,z=j}\r
389                         end\r
390                 end\r
391                 return ret\r
392         end\r
393         if env == nil then env = minetest.env end\r
394         -- connect the joined parts\r
395         local spiral = spiralt(width)\r
396         height = tonumber(height)\r
397         if height < 1 then height = 1 end\r
398         spacer = tonumber(spacer)-1\r
399         if spacer < 1 then spacer = 1 end\r
400         local count = 0\r
401         local node = {name=nodename}\r
402         local np,lp\r
403         for y=0,height do\r
404                 lp = nil\r
405                 for _,v in ipairs(spiral) do\r
406                         np = {x=pos.x+v.x*spacer, y=pos.y+y, z=pos.z+v.z*spacer}\r
407                         if lp~=nil then\r
408                                 if lp.x~=np.x then \r
409                                         if lp.x<np.x then \r
410                                                 for i=lp.x+1,np.x do\r
411                                                         env:add_node({x=i, y=np.y, z=np.z}, node)\r
412                                                         count = count + 1\r
413                                                 end\r
414                                         else\r
415                                                 for i=np.x,lp.x-1 do\r
416                                                         env:add_node({x=i, y=np.y, z=np.z}, node)\r
417                                                         count = count + 1\r
418                                                 end\r
419                                         end\r
420                                 end\r
421                                 if lp.z~=np.z then \r
422                                         if lp.z<np.z then \r
423                                                 for i=lp.z+1,np.z do\r
424                                                         env:add_node({x=np.x, y=np.y, z=i}, node)\r
425                                                         count = count + 1\r
426                                                 end\r
427                                         else\r
428                                                 for i=np.z,lp.z-1 do\r
429                                                         env:add_node({x=np.x, y=np.y, z=i}, node)\r
430                                                         count = count + 1\r
431                                                 end\r
432                                         end\r
433                                 end\r
434                         end\r
435                         lp = np\r
436                 end\r
437         end\r
438         return count\r
439 end\r