navmesh_chhunks_demo_2d.gd 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. extends Node2D
  2. static var map_cell_size: float = 1.0
  3. static var chunk_size: int = 256
  4. static var cell_size: float = 1.0
  5. static var agent_radius: float = 10.0
  6. static var chunk_id_to_region: Dictionary = {}
  7. var path_start_position: Vector2
  8. func _ready() -> void:
  9. NavigationServer2D.set_debug_enabled(true)
  10. path_start_position = %DebugPaths.global_position
  11. var map: RID = get_world_2d().navigation_map
  12. NavigationServer2D.map_set_cell_size(map, map_cell_size)
  13. # Disable performance costly edge connection margin feature.
  14. # This feature is not needed to merge navigation mesh edges.
  15. # If edges are well aligned they will merge just fine by edge key.
  16. NavigationServer2D.map_set_use_edge_connections(map, false)
  17. # Parse the collision shapes below our parse root node.
  18. var source_geometry: NavigationMeshSourceGeometryData2D = NavigationMeshSourceGeometryData2D.new()
  19. var parse_settings: NavigationPolygon = NavigationPolygon.new()
  20. parse_settings.parsed_geometry_type = NavigationPolygon.PARSED_GEOMETRY_STATIC_COLLIDERS
  21. NavigationServer2D.parse_source_geometry_data(parse_settings, source_geometry, %ParseRootNode)
  22. # Add an outline to define the traversable surface that the parsed collision shapes can "cut" into.
  23. var traversable_outline: PackedVector2Array = PackedVector2Array([
  24. Vector2(0.0, 0.0),
  25. Vector2(1920.0, 0.0),
  26. Vector2(1920.0, 1080.0),
  27. Vector2(0.0, 1080.0),
  28. ])
  29. source_geometry.add_traversable_outline(traversable_outline)
  30. create_region_chunks(%ChunksContainer, source_geometry, chunk_size * cell_size, agent_radius)
  31. static func create_region_chunks(chunks_root_node: Node, p_source_geometry: NavigationMeshSourceGeometryData2D, p_chunk_size: float, p_agent_radius: float) -> void:
  32. # We need to know how many chunks are required for the input geometry.
  33. # So first get an axis aligned bounding box that covers all vertices.
  34. var input_geometry_bounds: Rect2 = calculate_source_geometry_bounds(p_source_geometry)
  35. # Rasterize bounding box into chunk grid to know range of required chunks.
  36. var start_chunk: Vector2 = floor(
  37. input_geometry_bounds.position / p_chunk_size
  38. )
  39. var end_chunk: Vector2 = floor(
  40. (input_geometry_bounds.position + input_geometry_bounds.size)
  41. / p_chunk_size
  42. )
  43. for chunk_y in range(start_chunk.y, end_chunk.y + 1):
  44. for chunk_x in range(start_chunk.x, end_chunk.x + 1):
  45. var chunk_id: Vector2i = Vector2i(chunk_x, chunk_y)
  46. var chunk_bounding_box: Rect2 = Rect2(
  47. Vector2(chunk_x, chunk_y) * p_chunk_size,
  48. Vector2(p_chunk_size, p_chunk_size),
  49. )
  50. # We grow the chunk bounding box to include geometry
  51. # from all the neighbor chunks so edges can align.
  52. # The border size is the same value as our grow amount so
  53. # the final navigation mesh ends up with the intended chunk size.
  54. var baking_bounds: Rect2 = chunk_bounding_box.grow(p_chunk_size)
  55. var chunk_navmesh: NavigationPolygon = NavigationPolygon.new()
  56. chunk_navmesh.parsed_geometry_type = NavigationPolygon.PARSED_GEOMETRY_STATIC_COLLIDERS
  57. chunk_navmesh.baking_rect = baking_bounds
  58. chunk_navmesh.border_size = p_chunk_size
  59. chunk_navmesh.agent_radius = p_agent_radius
  60. NavigationServer2D.bake_from_source_geometry_data(chunk_navmesh, p_source_geometry)
  61. # The only reason we reset the baking bounds here is to not render its debug.
  62. chunk_navmesh.baking_rect = Rect2()
  63. # Snap vertex positions to avoid most rasterization issues with float precision.
  64. var navmesh_vertices: PackedVector2Array = chunk_navmesh.vertices
  65. for i in navmesh_vertices.size():
  66. var vertex: Vector2 = navmesh_vertices[i]
  67. navmesh_vertices[i] = vertex.snappedf(map_cell_size * 0.1)
  68. chunk_navmesh.vertices = navmesh_vertices
  69. var chunk_region: NavigationRegion2D = NavigationRegion2D.new()
  70. chunk_region.navigation_polygon = chunk_navmesh
  71. chunks_root_node.add_child(chunk_region)
  72. chunk_id_to_region[chunk_id] = chunk_region
  73. static func calculate_source_geometry_bounds(p_source_geometry: NavigationMeshSourceGeometryData2D) -> Rect2:
  74. if p_source_geometry.has_method("get_bounds"):
  75. # Godot 4.3 Patch added get_bounds() function that does the same but faster.
  76. return p_source_geometry.call("get_bounds")
  77. var bounds: Rect2 = Rect2()
  78. var first_vertex: bool = true
  79. for traversable_outline: PackedVector2Array in p_source_geometry.get_traversable_outlines():
  80. for traversable_point: Vector2 in traversable_outline:
  81. if first_vertex:
  82. first_vertex = false
  83. bounds.position = traversable_point
  84. else:
  85. bounds = bounds.expand(traversable_point)
  86. for obstruction_outline: PackedVector2Array in p_source_geometry.get_obstruction_outlines():
  87. for obstruction_point: Vector2 in obstruction_outline:
  88. if first_vertex:
  89. first_vertex = false
  90. bounds.position = obstruction_point
  91. else:
  92. bounds = bounds.expand(obstruction_point)
  93. for projected_obstruction: Dictionary in p_source_geometry.get_projected_obstructions():
  94. var projected_obstruction_vertices: PackedFloat32Array = projected_obstruction["vertices"]
  95. for i in projected_obstruction_vertices.size() / 2:
  96. var vertex: Vector2 = Vector2(projected_obstruction_vertices[i * 2], projected_obstruction_vertices[i * 2 + 1])
  97. if first_vertex:
  98. first_vertex = false
  99. bounds.position = vertex
  100. else:
  101. bounds = bounds.expand(vertex)
  102. return bounds
  103. func _process(_delta: float) -> void:
  104. var mouse_cursor_position: Vector2 = get_global_mouse_position()
  105. var map: RID = get_world_2d().navigation_map
  106. # Do not query when the map has never synchronized and is empty.
  107. if NavigationServer2D.map_get_iteration_id(map) == 0:
  108. return
  109. var closest_point_on_navmesh: Vector2 = NavigationServer2D.map_get_closest_point(
  110. map,
  111. mouse_cursor_position
  112. )
  113. if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
  114. path_start_position = closest_point_on_navmesh
  115. %DebugPaths.global_position = path_start_position
  116. %PathDebugCorridorFunnel.target_position = closest_point_on_navmesh
  117. %PathDebugEdgeCentered.target_position = closest_point_on_navmesh
  118. %PathDebugCorridorFunnel.get_next_path_position()
  119. %PathDebugEdgeCentered.get_next_path_position()