1 --- Generic node manipulations.
\r
2 -- @module worldedit.manipulations
\r
4 local mh = worldedit.manip_helpers
\r
7 --- Sets a region to `node_names`.
\r
10 -- @param node_names Node name or list of node names.
\r
11 -- @return The number of nodes set.
\r
12 function worldedit.set(pos1, pos2, node_names)
\r
13 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
15 local manip, area = mh.init(pos1, pos2)
\r
16 local data = mh.get_empty_data(area)
\r
18 if type(node_names) == "string" then -- Only one type of node
\r
19 local id = minetest.get_content_id(node_names)
\r
20 -- Fill area with node
\r
21 for i in area:iterp(pos1, pos2) do
\r
24 else -- Several types of nodes specified
\r
26 for i, v in ipairs(node_names) do
\r
27 node_ids[i] = minetest.get_content_id(v)
\r
29 -- Fill area randomly with nodes
\r
30 local id_count, rand = #node_ids, math.random
\r
31 for i in area:iterp(pos1, pos2) do
\r
32 data[i] = node_ids[rand(id_count)]
\r
36 mh.finish(manip, data)
\r
38 return worldedit.volume(pos1, pos2)
\r
41 --- Sets param2 of a region.
\r
44 -- @param param2 Value of param2 to set
\r
45 -- @return The number of nodes set.
\r
46 function worldedit.set_param2(pos1, pos2, param2)
\r
47 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
49 local manip, area = mh.init(pos1, pos2)
\r
50 local param2_data = manip:get_param2_data()
\r
52 -- Set param2 for every node
\r
53 for i in area:iterp(pos1, pos2) do
\r
54 param2_data[i] = param2
\r
58 manip:set_param2_data(param2_data)
\r
59 manip:write_to_map()
\r
62 return worldedit.volume(pos1, pos2)
\r
65 --- Replaces all instances of `search_node` with `replace_node` in a region.
\r
66 -- When `inverse` is `true`, replaces all instances that are NOT `search_node`.
\r
67 -- @return The number of nodes replaced.
\r
68 function worldedit.replace(pos1, pos2, search_node, replace_node, inverse)
\r
69 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
71 local manip, area = mh.init(pos1, pos2)
\r
72 local data = manip:get_data()
\r
74 local search_id = minetest.get_content_id(search_node)
\r
75 local replace_id = minetest.get_content_id(replace_node)
\r
80 for i in area:iterp(pos1, pos2) do
\r
81 if data[i] == search_id then
\r
82 data[i] = replace_id
\r
87 for i in area:iterp(pos1, pos2) do
\r
88 if data[i] ~= search_id then
\r
89 data[i] = replace_id
\r
95 mh.finish(manip, data)
\r
101 local function deferred_execution(next_one, finished)
\r
102 -- Allocate 100% of server step for execution (might lag a little)
\r
103 local allocated_usecs =
\r
104 tonumber(minetest.settings:get("dedicated_server_step")) * 1000000
\r
106 local deadline = minetest.get_us_time() + allocated_usecs
\r
108 local is_done = next_one()
\r
115 until minetest.get_us_time() >= deadline
\r
116 minetest.after(0, f)
\r
121 --- Duplicates a region `amount` times with offset vector `direction`.
\r
122 -- Stacking is spread across server steps.
\r
123 -- @return The number of nodes stacked.
\r
124 function worldedit.stack2(pos1, pos2, direction, amount, finished)
\r
125 -- Protect arguments from external changes during execution
\r
126 pos1 = table.copy(pos1)
\r
127 pos2 = table.copy(pos2)
\r
128 direction = table.copy(direction)
\r
131 local translated = {x=0, y=0, z=0}
\r
132 local function step()
\r
133 translated.x = translated.x + direction.x
\r
134 translated.y = translated.y + direction.y
\r
135 translated.z = translated.z + direction.z
\r
136 worldedit.copy2(pos1, pos2, translated)
\r
140 deferred_execution(step, finished)
\r
142 return worldedit.volume(pos1, pos2) * amount
\r
146 --- Copies a region along `axis` by `amount` nodes.
\r
149 -- @param axis Axis ("x", "y", or "z")
\r
151 -- @return The number of nodes copied.
\r
152 function worldedit.copy(pos1, pos2, axis, amount)
\r
153 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
155 -- Decide if we need to copy stuff backwards (only applies to metadata)
\r
156 local backwards = amount > 0 and amount < (pos2[axis] - pos1[axis] + 1)
\r
158 local off = {x=0, y=0, z=0}
\r
160 return worldedit.copy2(pos1, pos2, off, backwards)
\r
163 --- Copies a region by offset vector `off`.
\r
167 -- @param meta_backwards (not officially part of API)
\r
168 -- @return The number of nodes copied.
\r
169 function worldedit.copy2(pos1, pos2, off, meta_backwards)
\r
170 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
172 local src_manip, src_area = mh.init(pos1, pos2)
\r
173 local src_stride = {x=1, y=src_area.ystride, z=src_area.zstride}
\r
174 local src_offset = vector.subtract(pos1, src_area.MinEdge)
\r
176 local dpos1 = vector.add(pos1, off)
\r
177 local dpos2 = vector.add(pos2, off)
\r
178 local dim = vector.add(vector.subtract(pos2, pos1), 1)
\r
180 local dst_manip, dst_area = mh.init(dpos1, dpos2)
\r
181 local dst_stride = {x=1, y=dst_area.ystride, z=dst_area.zstride}
\r
182 local dst_offset = vector.subtract(dpos1, dst_area.MinEdge)
\r
184 local function do_copy(src_data, dst_data)
\r
185 for z = 0, dim.z-1 do
\r
186 local src_index_z = (src_offset.z + z) * src_stride.z + 1 -- +1 for 1-based indexing
\r
187 local dst_index_z = (dst_offset.z + z) * dst_stride.z + 1
\r
188 for y = 0, dim.y-1 do
\r
189 local src_index_y = src_index_z + (src_offset.y + y) * src_stride.y
\r
190 local dst_index_y = dst_index_z + (dst_offset.y + y) * dst_stride.y
\r
191 -- Copy entire row at once
\r
192 local src_index_x = src_index_y + src_offset.x
\r
193 local dst_index_x = dst_index_y + dst_offset.x
\r
194 for x = 0, dim.x-1 do
\r
195 dst_data[dst_index_x + x] = src_data[src_index_x + x]
\r
202 local src_data = src_manip:get_data()
\r
203 local dst_data = dst_manip:get_data()
\r
204 do_copy(src_data, dst_data)
\r
205 dst_manip:set_data(dst_data)
\r
208 src_manip:get_light_data(src_data)
\r
209 dst_manip:get_light_data(dst_data)
\r
210 do_copy(src_data, dst_data)
\r
211 dst_manip:set_light_data(dst_data)
\r
214 src_manip:get_param2_data(src_data)
\r
215 dst_manip:get_param2_data(dst_data)
\r
216 do_copy(src_data, dst_data)
\r
217 dst_manip:set_param2_data(dst_data)
\r
219 mh.finish(dst_manip)
\r
224 local get_meta = minetest.get_meta
\r
225 if meta_backwards then
\r
226 for z = dim.z-1, 0, -1 do
\r
227 for y = dim.y-1, 0, -1 do
\r
228 for x = dim.x-1, 0, -1 do
\r
229 local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}
\r
230 local meta = get_meta(pos):to_table()
\r
231 pos = vector.add(pos, off)
\r
232 get_meta(pos):from_table(meta)
\r
237 for z = 0, dim.z-1 do
\r
238 for y = 0, dim.y-1 do
\r
239 for x = 0, dim.x-1 do
\r
240 local pos = {x=pos1.x+x, y=pos1.y+y, z=pos1.z+z}
\r
241 local meta = get_meta(pos):to_table()
\r
242 pos = vector.add(pos, off)
\r
243 get_meta(pos):from_table(meta)
\r
249 return worldedit.volume(pos1, pos2)
\r
252 --- Deletes all node metadata in the region
\r
255 -- @return The number of nodes that had their meta deleted.
\r
256 function worldedit.delete_meta(pos1, pos2)
\r
257 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
259 local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)
\r
260 local get_meta = minetest.get_meta
\r
261 for _, pos in ipairs(meta_positions) do
\r
262 get_meta(pos):from_table(nil)
\r
265 return #meta_positions
\r
268 --- Moves a region along `axis` by `amount` nodes.
\r
269 -- @return The number of nodes moved.
\r
270 function worldedit.move(pos1, pos2, axis, amount)
\r
271 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
273 local dim = vector.add(vector.subtract(pos2, pos1), 1)
\r
274 local overlap = math.abs(amount) < dim[axis]
\r
275 -- Decide if we need to copy metadata backwards
\r
276 local backwards = overlap and amount > 0
\r
278 local function nuke_area(my_off, my_dim)
\r
279 if my_dim.x == 0 or my_dim.y == 0 or my_dim.z == 0 then
\r
282 local my_pos1 = vector.add(pos1, my_off)
\r
283 local my_pos2 = vector.subtract(vector.add(my_pos1, my_dim), 1)
\r
284 worldedit.set(my_pos1, my_pos2, "air")
\r
285 worldedit.delete_meta(my_pos1, my_pos2)
\r
288 -- Copy stuff to new location
\r
289 local off = {x=0, y=0, z=0}
\r
291 worldedit.copy2(pos1, pos2, off, backwards)
\r
293 if not overlap then
\r
294 nuke_area({x=0, y=0, z=0}, dim)
\r
296 -- Source and destination region are overlapping, which means we can't
\r
297 -- blindly delete the [pos1, pos2] area
\r
298 local leftover = vector.new(dim) -- size of the leftover slice
\r
299 leftover[axis] = math.abs(amount)
\r
301 nuke_area({x=0, y=0, z=0}, leftover)
\r
303 local top = {x=0, y=0, z=0} -- offset of the leftover slice from pos1
\r
304 top[axis] = dim[axis] - math.abs(amount)
\r
305 nuke_area(top, leftover)
\r
309 return worldedit.volume(pos1, pos2)
\r
312 --- Duplicates a region along `axis` `amount` times.
\r
313 -- Stacking is spread across server steps.
\r
316 -- @param axis Axis direction, "x", "y", or "z".
\r
318 -- @return The number of nodes stacked.
\r
319 function worldedit.stack(pos1, pos2, axis, count, finished)
\r
320 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
321 local length = pos2[axis] - pos1[axis] + 1
\r
327 local i, distance = 0, 0
\r
328 local function step()
\r
329 distance = distance + length
\r
330 worldedit.copy(pos1, pos2, axis, distance)
\r
334 deferred_execution(step, finished)
\r
336 return worldedit.volume(pos1, pos2) * count
\r
340 --- Stretches a region by a factor of positive integers along the X, Y, and Z
\r
341 -- axes, respectively, with `pos1` as the origin.
\r
344 -- @param stretch_x Amount to stretch along X axis.
\r
345 -- @param stretch_y Amount to stretch along Y axis.
\r
346 -- @param stretch_z Amount to stretch along Z axis.
\r
347 -- @return The number of nodes scaled.
\r
348 -- @return The new scaled position 1.
\r
349 -- @return The new scaled position 2.
\r
350 function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z)
\r
351 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
353 -- Prepare schematic of large node
\r
354 local get_node, get_meta, place_schematic = minetest.get_node,
\r
355 minetest.get_meta, minetest.place_schematic
\r
356 local placeholder_node = {name="", param1=255, param2=0}
\r
358 for i = 1, stretch_x * stretch_y * stretch_z do
\r
359 nodes[i] = placeholder_node
\r
361 local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes}
\r
363 local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1
\r
366 x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,
\r
367 y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,
\r
368 z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,
\r
370 worldedit.keep_loaded(pos1, new_pos2)
\r
372 local pos = {x=pos2.x, y=0, z=0}
\r
373 local big_pos = {x=0, y=0, z=0}
\r
374 while pos.x >= pos1.x do
\r
376 while pos.y >= pos1.y do
\r
378 while pos.z >= pos1.z do
\r
379 local node = get_node(pos) -- Get current node
\r
380 local meta = get_meta(pos):to_table() -- Get meta of current node
\r
382 -- Calculate far corner of the big node
\r
383 local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x
\r
384 local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y
\r
385 local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z
\r
387 -- Create large node
\r
388 placeholder_node.name = node.name
\r
389 placeholder_node.param2 = node.param2
\r
390 big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z
\r
391 place_schematic(big_pos, schematic)
\r
393 -- Fill in large node meta
\r
394 if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
\r
395 -- Node has meta fields
\r
396 for x = 0, size_x do
\r
397 for y = 0, size_y do
\r
398 for z = 0, size_z do
\r
399 big_pos.x = pos_x + x
\r
400 big_pos.y = pos_y + y
\r
401 big_pos.z = pos_z + z
\r
402 -- Set metadata of new node
\r
403 get_meta(big_pos):from_table(meta)
\r
414 return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2
\r
418 --- Transposes a region between two axes.
\r
419 -- @return The number of nodes transposed.
\r
420 -- @return The new transposed position 1.
\r
421 -- @return The new transposed position 2.
\r
422 function worldedit.transpose(pos1, pos2, axis1, axis2)
\r
423 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
426 local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2]
\r
428 if extent1 > extent2 then
\r
429 compare = function(extent1, extent2)
\r
430 return extent1 > extent2
\r
433 compare = function(extent1, extent2)
\r
434 return extent1 < extent2
\r
438 -- Calculate the new position 2 after transposition
\r
439 local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
\r
440 new_pos2[axis1] = pos1[axis1] + extent2
\r
441 new_pos2[axis2] = pos1[axis2] + extent1
\r
443 local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z}
\r
444 if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end
\r
445 if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end
\r
446 worldedit.keep_loaded(pos1, upper_bound)
\r
448 local pos = {x=pos1.x, y=0, z=0}
\r
449 local get_node, get_meta, set_node = minetest.get_node,
\r
450 minetest.get_meta, minetest.set_node
\r
451 while pos.x <= pos2.x do
\r
453 while pos.y <= pos2.y do
\r
455 while pos.z <= pos2.z do
\r
456 local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
\r
457 if compare(extent1, extent2) then -- Transpose only if below the diagonal
\r
458 local node1 = get_node(pos)
\r
459 local meta1 = get_meta(pos):to_table()
\r
460 local value1, value2 = pos[axis1], pos[axis2] -- Save position values
\r
461 pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents
\r
462 local node2 = get_node(pos)
\r
463 local meta2 = get_meta(pos):to_table()
\r
464 set_node(pos, node1)
\r
465 get_meta(pos):from_table(meta1)
\r
466 pos[axis1], pos[axis2] = value1, value2 -- Restore position values
\r
467 set_node(pos, node2)
\r
468 get_meta(pos):from_table(meta2)
\r
476 return worldedit.volume(pos1, pos2), pos1, new_pos2
\r
480 --- Flips a region along `axis`.
\r
481 -- @return The number of nodes flipped.
\r
482 function worldedit.flip(pos1, pos2, axis)
\r
483 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
485 worldedit.keep_loaded(pos1, pos2)
\r
487 --- TODO: Flip the region slice by slice along the flip axis using schematic method.
\r
488 local pos = {x=pos1.x, y=0, z=0}
\r
489 local start = pos1[axis] + pos2[axis]
\r
490 pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2)
\r
491 local get_node, get_meta, set_node = minetest.get_node,
\r
492 minetest.get_meta, minetest.set_node
\r
493 while pos.x <= pos2.x do
\r
495 while pos.y <= pos2.y do
\r
497 while pos.z <= pos2.z do
\r
498 local node1 = get_node(pos)
\r
499 local meta1 = get_meta(pos):to_table()
\r
500 local value = pos[axis] -- Save position
\r
501 pos[axis] = start - value -- Shift position
\r
502 local node2 = get_node(pos)
\r
503 local meta2 = get_meta(pos):to_table()
\r
504 set_node(pos, node1)
\r
505 get_meta(pos):from_table(meta1)
\r
506 pos[axis] = value -- Restore position
\r
507 set_node(pos, node2)
\r
508 get_meta(pos):from_table(meta2)
\r
515 return worldedit.volume(pos1, pos2)
\r
519 --- Rotates a region clockwise around an axis.
\r
522 -- @param axis Axis ("x", "y", or "z").
\r
523 -- @param angle Angle in degrees (90 degree increments only).
\r
524 -- @return The number of nodes rotated.
\r
525 -- @return The new first position.
\r
526 -- @return The new second position.
\r
527 function worldedit.rotate(pos1, pos2, axis, angle)
\r
528 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
530 local other1, other2 = worldedit.get_axis_others(axis)
\r
531 angle = angle % 360
\r
534 if angle == 90 then
\r
535 worldedit.flip(pos1, pos2, other1)
\r
536 count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
\r
537 elseif angle == 180 then
\r
538 worldedit.flip(pos1, pos2, other1)
\r
539 count = worldedit.flip(pos1, pos2, other2)
\r
540 elseif angle == 270 then
\r
541 worldedit.flip(pos1, pos2, other2)
\r
542 count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
\r
544 error("Only 90 degree increments are supported!")
\r
546 return count, pos1, pos2
\r
550 --- Rotates all oriented nodes in a region clockwise around the Y axis.
\r
553 -- @param angle Angle in degrees (90 degree increments only).
\r
554 -- @return The number of nodes oriented.
\r
555 function worldedit.orient(pos1, pos2, angle)
\r
556 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
557 local registered_nodes = minetest.registered_nodes
\r
559 local wallmounted = {
\r
560 [90] = {0, 1, 5, 4, 2, 3, 0, 0},
\r
561 [180] = {0, 1, 3, 2, 5, 4, 0, 0},
\r
562 [270] = {0, 1, 4, 5, 3, 2, 0, 0}
\r
565 [90] = { 1, 2, 3, 0, 13, 14, 15, 12, 17, 18, 19, 16,
\r
566 9, 10, 11, 8, 5, 6, 7, 4, 23, 20, 21, 22},
\r
567 [180] = { 2, 3, 0, 1, 10, 11, 8, 9, 6, 7, 4, 5,
\r
568 18, 19, 16, 17, 14, 15, 12, 13, 22, 23, 20, 21},
\r
569 [270] = { 3, 0, 1, 2, 19, 16, 17, 18, 15, 12, 13, 14,
\r
570 7, 4, 5, 6, 11, 8, 9, 10, 21, 22, 23, 20}
\r
573 angle = angle % 360
\r
577 if angle % 90 ~= 0 then
\r
578 error("Only 90 degree increments are supported!")
\r
580 local wallmounted_substitution = wallmounted[angle]
\r
581 local facedir_substitution = facedir[angle]
\r
583 worldedit.keep_loaded(pos1, pos2)
\r
586 local get_node, swap_node = minetest.get_node, minetest.swap_node
\r
587 local pos = {x=pos1.x, y=0, z=0}
\r
588 while pos.x <= pos2.x do
\r
590 while pos.y <= pos2.y do
\r
592 while pos.z <= pos2.z do
\r
593 local node = get_node(pos)
\r
594 local def = registered_nodes[node.name]
\r
596 local paramtype2 = def.paramtype2
\r
597 if paramtype2 == "wallmounted" or
\r
598 paramtype2 == "colorwallmounted" then
\r
599 local orient = node.param2 % 8
\r
600 node.param2 = node.param2 - orient +
\r
601 wallmounted_substitution[orient + 1]
\r
602 swap_node(pos, node)
\r
604 elseif paramtype2 == "facedir" or
\r
605 paramtype2 == "colorfacedir" then
\r
606 local orient = node.param2 % 32
\r
607 node.param2 = node.param2 - orient +
\r
608 facedir_substitution[orient + 1]
\r
609 swap_node(pos, node)
\r
623 --- Attempts to fix the lighting in a region.
\r
624 -- @return The number of nodes updated.
\r
625 function worldedit.fixlight(pos1, pos2)
\r
626 local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
628 local vmanip = minetest.get_voxel_manip(pos1, pos2)
\r
629 vmanip:write_to_map()
\r
630 vmanip:update_map() -- this updates the lighting
\r
632 return worldedit.volume(pos1, pos2)
\r
636 --- Clears all objects in a region.
\r
637 -- @return The number of objects cleared.
\r
638 function worldedit.clear_objects(pos1, pos2)
\r
639 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
\r
641 worldedit.keep_loaded(pos1, pos2)
\r
643 local function should_delete(obj)
\r
644 -- Avoid players and WorldEdit entities
\r
645 if obj:is_player() then
\r
648 local entity = obj:get_luaentity()
\r
649 return not entity or not entity.name:find("^worldedit:")
\r
652 -- Offset positions to include full nodes (positions are in the center of nodes)
\r
653 local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5
\r
654 local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5
\r
657 if minetest.get_objects_in_area then
\r
658 local objects = minetest.get_objects_in_area({x=pos1x, y=pos1y, z=pos1z},
\r
659 {x=pos2x, y=pos2y, z=pos2z})
\r
661 for _, obj in pairs(objects) do
\r
662 if should_delete(obj) then
\r
670 -- Fallback implementation via get_objects_inside_radius
\r
671 -- Center of region
\r
673 x = pos1x + ((pos2x - pos1x) / 2),
\r
674 y = pos1y + ((pos2y - pos1y) / 2),
\r
675 z = pos1z + ((pos2z - pos1z) / 2)
\r
677 -- Bounding sphere radius
\r
678 local radius = math.sqrt(
\r
679 (center.x - pos1x) ^ 2 +
\r
680 (center.y - pos1y) ^ 2 +
\r
681 (center.z - pos1z) ^ 2)
\r
682 for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do
\r
683 if should_delete(obj) then
\r
684 local pos = obj:get_pos()
\r
685 if pos.x >= pos1x and pos.x <= pos2x and
\r
686 pos.y >= pos1y and pos.y <= pos2y and
\r
687 pos.z >= pos1z and pos.z <= pos2z then
\r