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