raycast.lua 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. local function raycast_with_pointabilities(start_pos, end_pos, pointabilities)
  2. local ray = core.raycast(start_pos, end_pos, nil, nil, pointabilities)
  3. for hit in ray do
  4. if hit.type == "node" then
  5. return hit.under
  6. end
  7. end
  8. return nil
  9. end
  10. local function test_raycast_pointabilities(player, pos1)
  11. local pos2 = pos1:offset(0, 0, 1)
  12. local pos3 = pos1:offset(0, 0, 2)
  13. local oldnode1 = core.get_node(pos1)
  14. local oldnode2 = core.get_node(pos2)
  15. local oldnode3 = core.get_node(pos3)
  16. core.swap_node(pos1, {name = "air"})
  17. core.swap_node(pos2, {name = "testnodes:not_pointable"})
  18. core.swap_node(pos3, {name = "testnodes:pointable"})
  19. local p = nil
  20. assert(raycast_with_pointabilities(pos1, pos3, p) == pos3)
  21. p = core.registered_items["testtools:blocked_pointing_staff"].pointabilities
  22. assert(raycast_with_pointabilities(pos1, pos3, p) == nil)
  23. p = core.registered_items["testtools:ultimate_pointing_staff"].pointabilities
  24. assert(raycast_with_pointabilities(pos1, pos3, p) == pos2)
  25. core.swap_node(pos1, oldnode1)
  26. core.swap_node(pos2, oldnode2)
  27. core.swap_node(pos3, oldnode3)
  28. end
  29. unittests.register("test_raycast_pointabilities", test_raycast_pointabilities, {map=true})
  30. local function test_raycast_noskip(_, pos)
  31. local function random_point_in_area(min, max)
  32. local extents = max - min
  33. local v = extents:multiply(vector.new(
  34. math.random(),
  35. math.random(),
  36. math.random()
  37. ))
  38. return min + v
  39. end
  40. -- FIXME a variation of this unit test fails in an edge case.
  41. -- This is because Luanti does not handle perfectly diagonal raycasts correctly:
  42. -- Perfect diagonals collide with neither "outside" face and may thus "pierce" nodes.
  43. -- Enable the following code to reproduce:
  44. if 0 == 1 then
  45. pos = vector.new(6, 32, -3)
  46. math.randomseed(1596190898)
  47. function random_point_in_area(min, max)
  48. return min:combine(max, math.random)
  49. end
  50. end
  51. local function cuboid_minmax(extent)
  52. return pos:offset(-extent, -extent, -extent),
  53. pos:offset(extent, extent, extent)
  54. end
  55. -- Carve out a 3x3x3 dirt cuboid in a larger air cuboid
  56. local r = 8
  57. local min, max = cuboid_minmax(r + 1)
  58. local vm = core.get_voxel_manip(min, max)
  59. local old_data = vm:get_data()
  60. local data = vm:get_data()
  61. local emin, emax = vm:get_emerged_area()
  62. local va = VoxelArea:new({MinEdge = emin, MaxEdge = emax})
  63. for index in va:iterp(min, max) do
  64. data[index] = core.CONTENT_AIR
  65. end
  66. for index in va:iterp(cuboid_minmax(1)) do
  67. data[index] = core.get_content_id("basenodes:dirt")
  68. end
  69. vm:set_data(data)
  70. vm:write_to_map()
  71. -- Raycast many times from outside the cuboid
  72. for _ = 1, 100 do
  73. local ray_start
  74. repeat
  75. ray_start = random_point_in_area(cuboid_minmax(r))
  76. until not ray_start:in_area(cuboid_minmax(1.501))
  77. -- Pick a random position inside the dirt
  78. local ray_end = random_point_in_area(cuboid_minmax(1.499))
  79. -- The first pointed thing should have only air "in front" of it,
  80. -- or a dirt node got falsely skipped.
  81. local pt = core.raycast(ray_start, ray_end, false, false):next()
  82. if pt then
  83. assert(core.get_node(pt.above).name == "air")
  84. end
  85. end
  86. vm:set_data(old_data)
  87. vm:write_to_map()
  88. end
  89. unittests.register("test_raycast_noskip", test_raycast_noskip, {map = true, random = true})